#Functions that return slices confusion

1 messages ยท Page 1 of 1 (latest)

bright cloud
#
const std = @import("std");

fn rvo() []usize {
    var a = [_]usize{ 1, 2, 3 };
    return &a;
}

pub fn main() !void {
    const a = rvo();
    const b = rvo();
    std.debug.print("{any}\n{any}", .{ a, b });
}

I'm a bit confused with this; the output is:
{ 12391456, 587280153432, 587280153432 }
{ 12391456, 587280153432, 587280153432 }

In most languages I feel like this is either copied to the call-site stack frame or initially allocated on the call-site using RVO. In zig I get neither and I've run into this issue many times. If I want to do something as simple as a function call and I want to return an array from that function without allocating what should I do? ๐Ÿ™‚ thanks a lot in advance!

onyx oracle
#

you can just return the array

#

[3]usize is the type

#

or use an out parameter

#

where you pass in a slice to the function, and the function writes to it

bright cloud
#

ah I see so my confusion here is between array and slices+

tacit canyon
#

yes, slices are just ptr and length

bright cloud
#

does zig optimize this ?

onyx oracle
#

wdym optimize

bright cloud
#

such that the array created in the stack frame of rvo isn't copied to the callsite

tacit canyon
#

pass slice as a argument instead if you don't want a copy

bright cloud
#

or whats the idiomatic way to do this? is it to provide a buffer?

onyx oracle
#

just return the array directly

#

dont return a pointer/slice

bright cloud
#

isn't that pointer invalid?

onyx oracle
#

correct

#

thats the problem

bright cloud
#

right so if I change it to:

const std = @import("std");

fn rvo() [3]usize {
    var a = [_]usize{ 1, 2, 3 };
    a[1] = 5;
    return a;
}

pub fn main() !void {
    const a = rvo();
    const b = rvo();
    std.debug.print("{any}\n{any}", .{ a, b });
}
#

is this copying the array?

onyx oracle
#

yes

#

optimizations may inline that, but semantically its a copy

bright cloud
#

so there's no copy elision in zig? (other than what you get from when a function is inlined)

onyx oracle
#

how would you elide this specific copy

#

you're creating something on the stack, then modifying it

#

if you did return .{1, 2, 3} RLS (result location semantics) would elide the copy and just write directly to the result location, but you arent doing that

bright cloud
#

right; so thats whats called urvo in c++ afaik

#

but there's also named rvo in c++

tacit canyon
#
const std = @import("std");

fn rvo(out: *[3]usize) void {
    out[1] = 5;
}

pub fn main() !void {
    var a = [_]usize{ 1, 2, 3 };
    rvo(&a);
    std.debug.print("{any}\n", .{ a });
}

do this if you don't want a copy

bright cloud
#

I understand @tacit canyon thanks, it it answers my question fully. ๐Ÿ™‚ im just trying to understand if there are any guarantees wrt. copy elision so I can write functions without having to provide buffers/pointers

onyx oracle
#

aside from RLS, no, things are explicit in zig

#

either return copies, or take an out pointer, or allocate

tacit canyon
onyx oracle
bright cloud
#

oh right its called RLS in zig thanks

tacit canyon
bright cloud
#

no but some programs instead uses the call-site stack; so you skip a copy

onyx oracle
bright cloud
#

yes; thats very explicit, and probably follows the design philosophy of zig, but this is something that might be a bit of a learning curve when the behaviour differs from other languages