Call by value / call by reference

I want to pass a struct to a function with “call by reference”?
Is this done via good old fashioned pointers like in C or more
advanced with ‘&’-references like in C++? Or anything totally
different?
I searched “call by reference zig” the internet…but found
nothing…

Cheers!
tuxic

just normal pointers

const MyStruct = struct {};

fn foo(arg: *MyStruct) void {
   // ...
}

Do *const MyStruct if you don’t want it to be mutable.

const std = @import("std");

const Object = struct {
    a: i32,
    b: i32,
};

fn show(o: *Object) void {
    std.debug.print("{}\n", .{o.a + o.b});
}

pub fn main() !void {
    var objectOnStack = Object {
        .a = 1,
        .b = 2,
    };
    show(&objectOnStack);

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.print("leakage?.. {}\n", .{gpa.deinit()});
    const allocator = gpa.allocator();

    var objectOnHeap = try allocator.create(Object);
    objectOnHeap.a = 3;
    objectOnHeap.b = 4;
    show(objectOnHeap);
    allocator.destroy(objectOnHeap);
}
$ ./byref 
3
7
leakage?.. false

Also note that in most cases you might not need to do anything if your worry is just avoiding copies of the function argument:

From Documentation - The Zig Programming Language

Primitive types such as Integers and Floats passed as parameters are copied, and then the copy is available in the function body. This is called “passing by value”. Copying a primitive type is essentially free and typically involves nothing more than setting a register.

Structs, unions, and arrays can sometimes be more efficiently passed as a reference, since a copy could be arbitrarily expensive depending on the size. When these types are passed as parameters, Zig may choose to copy and pass by value, or pass by reference, whichever way Zig decides will be faster. This is made possible, in part, by the fact that parameters are immutable.

If you need mutability, then a pointer is the way to go as the other posters have shown above.

I think the rough intention is:

  • if you’d use const& T in C++ (or &T in Rust), just use T. “by value” parameters are implicitly by reference.
  • if you’d use &T, &mut T, pass *T

Though, this intention is not fully realized yet: there are some bugs in language semantics/compiler which cause strange results in the presence of aliasing:

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

1 Like