Understanding Function Attachments to Structs in Zig: Value vs Reference

I’m currently exploring how functions can be attached to structs in Zig and I’ve come across something that I find a bit confusing. Here’s a snippet of my code:

const Bar = struct{
    pub fn a(self: Bar) void {}
    pub fn b(this: *Bar, other: u8) void {}
    pub fn c(bar: *const Bar) void {}
};
...
var bar = Bar{};
bar.a() // is equivalent to Bar.a(bar)
bar.b(3) // is equivalent to Bar.b(&bar, 3)
bar.c() // is equivalent to Bar.c(&bar)

In the second and third cases (b and c), it makes sense to me that this and bar are references to the instance of Bar. However, in the first case (a), is self a value or a reference? Is self: Bar just syntactic sugar?

Also, when calling bar.a() or Bar.a(bar), is the bar instance copied? It seems like it would be, but I wanted to confirm if this is the case or if there’s something else going on under the hood.

Any insights would be greatly appreciated!

I feel like this question keeps getting asked, maybe there should be a doc about it.

4 Likes

the compiler passes the instance either by **value or by constant reference**..., but either way you **won’t be able to mutate** the instance

This is very confusing, there is no const, there is nothing telling me that the argument can not be mutated, neither there is anything telling me that it can be reference.

1 Like

It shouldn’t be confusing that arguments passed by value are immutable. The fact that you pass it by value, e.g. self: Bar, means that you don’t intend to mutate it. And since you won’t mutate it the compiler takes it onto itself to optimize passing the argument however it decides would be best.

2 Likes

Is the behavior constant?
if I have function abc(bar: Foo) is the bar const here as well?
or it’s only for “self” keyword

There is definitely a little confusion here. One thing to keep in mind that can help clarify is that, in Zig, parameters are always immutable.

With this in mind, it makes a little more sense that a pass-by-value parameter and constant reference parameter can be treated as effectively the same thing. Neither should be mutated, they should just be treated as a copy.

If you want to mutate the underlying data of a parameter, you should pass a normal (non-constant) reference to that data.

2 Likes

Yes, unless Foo is an alias for a mutable pointer type, e.g. const Foo = *Bar;

1 Like

Now I understand! Thanks Jora!

2 Likes

There is no self keyword, it’s a regular parameter name.

3 Likes

Thanks for the clarification.