What's the difference between `self: MyType` and `self: *MyType`?

pub const MyType = struct {
  pub fn f(self: MyType) !void {
  }
  pub fn g(self: *MyType) !void {
  }
}

It seems both method can be called with myTypeInstance.method(). Zig should not have implicit copy consturtion. So what is the difference?

1 Like

Hi! The difference is that in the case of my_type_instance.f() the compiler passes the instance either by value or by constant reference depending on what it thinks would be best, but either way you won’t be able to mutate the instance inside the function. In the case of my_type_instance.g(), you’re telling the compiler to pass the instance by pointer and allow you to mutate the instance behind that pointer.

There’s a 3rd alternative: fn h(self: *const MyType) void {} which is a kind of combination of the first two because this forces the compiler to pass the instance by pointer, but you won’t be able to mutate that instance.

6 Likes

Thank you! But what does it mean for passing by value? IIUC Zig doesn’t have implicit copy construction. So if my struct is complex or even allocated, how does it get pass by value?

Well, since the param is immutable, it’s fine a programmer doesn’t find the difference. Passing by value should mean a shallow copy.

Yeah, so for instance in the trivial case when it’s a big constant struct, which you’re never mutating, the compiler would pass by constant reference, trying to avoid an expensive copy. Cases when you have a variable that you’re mutating elsewhere, the compiler will create a copy. That’s why it’s better to pass big and/or heap-allocated things by pointers. At least that’s the way I understand it.

1 Like

There is interesting footgun related to this Footgun: hidden pass-by-reference · Issue #5973 · ziglang/zig · GitHub

4 Likes