Zig May Pass Anything By Reference

Zig may pass non-primitive parameters into function by reference. It may also pass non-primitive result location by reference.

To run the code snippets below, past the code into a file and run it with zig run file.zig (only guaranteed to work with Zig 0.11.0).

Alternatively, you can also run the code on this Zig Playground. Thanks dpc_pw!

The code snippets are provided by Lemon Drop and Levy in the Zig Matrix room. (feel free to join!)


Here is a buggy program that you can run with Zig 0.11.0. The output will be unexpected!

const AAAA = struct {
    foo: [100]u32,
};

fn aaaaa(a: AAAA, b: *AAAA) void {
  b.*.foo[0] = 5;

  std.debug.print("wtf: {}", .{ a.foo[0] });
}

pub fn main() !void {
    var f: AAAA = undefined;

    f.foo[0] = 0;

    aaaaa(f, &f);
}

Lesson: do not alias, ever.


Here is another buggy program. x is a result location, and is taken by reference on both sides of the assignment.

const std = @import("std");
const What = struct { a: u8, b: u8 };

pub fn main() void {
    var x: What = .{ .a = 1, .b = 2 };
    x = .{ .a = x.b, .b = x.a };
    std.log.info("wtf: {}", .{x});
}

Lesson: do not put the same variable on both sides of an assignment.

Solution: Use a temp variable to store the new value, like how you swap two variables in C.

pub fn main() void {
    var x: What = .{ .a = 1, .b = 2 };
    const tmp = .{ .a = x.b, .b = x.a };
    x = tmp;
    std.log.info("wtf: {}", .{x});
}

Thoughts

I have long thought Zig works like C: when I pass a struct itself (not a pointer), it is passed by-copy. I am lucky to have never met a bug because of this misunderstanding.

oof

Further Readings

https://github.com/ziglang/zig/issues/12064

andrewrk has recommended watching this video on the topic of result location semantics.