How to refer to the result location

There’s an open issue on GitHub that I don’t see anywhere on Codeberg, which (correct me if I’m wrong) means it’s still open and simply hasn’t been migrated yet:

If this is resolved or otherwise closed please tell me how I could have known :slight_smile: .

Anyway: since I can’t post there, here goes:

What if we introduce a little extra optional syntax to function declarations that allow them to name the result variable:

fn foo() res: Point {
    res = bar();
    return res;
}

Alternatives in the same vein:

  • require the variable to be var declared in the signature:
fn foo() var res: Point { …
  • ban return statement from such functions and always return the res
2 Likes

Interesting, this is similar to Go’s named return values:

func foo() (res int) {
    res = 123
    return
}    

AFAIK it’s just a convenience feature there though, and they require a bare return no matter what which implicitly returns the named value.

I think a good approach for Zig would be to also force a bare return as if the function would return void and then just make it an AstGen error if res is never used as an lvalue in the function body (similar to local variables):

fn foo() res: i32 {
    return;
}
error: unused named return value

The policy for old github issues is that anyone can reopen them on codeberg (with a link to the github issue) if they have something new to add to the discussion:

EDIT: reading through the existing issue, something very similar seems to already have been suggested here

Without having strong opinion on either side (for/against the proposal), I think a relevant use case that I often come across is when fields depend on each other, eg. when I want to alloc+init a struct while creating its own arena allocator.

const Foo {
    arena: std.mem.Allocator,
    things: []Thing,

    const Thing = struct {foo: u8, bar: u32}; // or whatever..

    // The example is not great, since having init with
    // a zeroed "things" like this is questionable.
    fn init(allocator: std.mem.Allocator, n: usize) !Foo {
        var self: Foo = .{
            arena: std.heap.ArenaAllocator = .init(allocator),
            things: undefined,
        };
        try self.things = self.arena.allocator().alloc(Thing, n);
        @memset(self.things, .{foo = 0, .bar = 0});
        return self;
    }
}

Thing is, I always feel dirty leaving undefineds anywhere, and although I’ve seen this pattern even in std, it does tend to raise my “stack pointer” anxiety a bit.

That said, I find that most of the times I can avoid such inter-dependent fields and end up with better API and more readable code anyway, so yeah, it’s a case in point, but as is, not a good argument for the proposal (weak one at best).

2 Likes

Given the known stance on discouraging managed containers, a motivating example for the idea without allocators may be more compelling.

1 Like

Why don’t we just make it legal to return pointers from inline function? It already works like this currently:

const std = @import("std");

const S = struct {
    num1: i32 = 0,
    num2: i32 = 0,
    num3: i32 = 0,
    self: *@This(),

    pub inline fn init() *@This() {
        var self: @This() = undefined;
        self = .{ .self = &self };
        return &self;
    }
};

pub fn main() !void {
    const s = S.init();
    std.debug.print("     s: @{x}\n", .{@intFromPtr(s)});
    std.debug.print("s.self: @{x}\n", .{@intFromPtr(s.self)});
}
     s: @7ffccf9e7e00
s.self: @7ffccf9e7e00

This is basically a generalized version of what’s being purposed. Instead of getting the address of one local variable in the caller’s scope, we’re letting the callee allocate as many as it wants.

To avoid copying, the best solution currently is this:

fn foo(res: *Point) void {
    res.* = bar();
    return;
}

I completely understand the reasons for not liking this solution: you have to declare the return symbol as var instead of const. I am also very much looking forward to this proposal being eventually resolved. In fact, I not only hope it can be resolved within functions, but also within block expressions.