test.zig:15:10: error: expected type '*test.Item', found '*const test.Item'
i.doSomething();
~^~~~~~~~~~~~
test.zig:15:10: note: cast discards const qualifier
test.zig:2:23: note: parameter type declared here
fn doSomething(_: *Item) void {}
No matter what I do, I can’t call doSomething on the captured variable.
Surprisingly, the problem persists even if I assign it to an intermediate var. This is the pattern I use when I’d like to mutate function parameters … but it does not work in this context.
// <snip>
while (iter.next()) |*i| {
var tmp = i;
tmp.doSomething();
}
Why is this not possible?
I have tried to search for “while const capture” on both the discord and the issue tracker. The closes related thing i found was this issue. … but it doesn’t seem exactly related (granted i’m not sure i understand it)
An iterator returning ?Item means that you are returning a copy of Item, if you want to mutate that item it doesn’t make sense to use an iterator that returns a copy.
A lot of iterators will return you a struct that just contains pointers, like the entry struct that contains pointers to the key and value that the HashMap.iterator returns from the next function. (Or iterators that return ?[]SomeSliceElement are also (optionals of) pointers (with a length))
If you want to mutate the returned item, then your iterator should return an optional pointer to the item, so it would use:
fn next(_: *Iter) ?*Item {
return null;
}
That means that your loop would be written like this:
while (iter.next()) |ptr| {
ptr.doSomething();
}
One example of this within the standard library is the reverse iterator, which has next and nextPtr depending on whether you want to use copying or pointer access.
Interesting … in my case, my iterator cannot return a pointer :/. The returned items are constructed in the .next() method on the stack.
An iterator returning ?Item means that you are returning a copy of Item, if you want to mutate that item it doesn’t make sense to use an iterator that returns a copy.
Hmm, but if I call the .next() method normally and put the result in a var, i am able to mutate the result. So it does not feel like this has anything to do with me returning a copy from .next()
This compiles:
pub const Item = struct {
fn doSomething(_: *Item) void {}
};
pub const Iter = struct {
fn next(_: *Iter) ?Item {
return null;
}
};
pub fn main() void {
var iter: Iter = .{};
// CHANGED
var first = iter.next() orelse return;
first.doSomething();
}
If this was allowed without putting the result into a var, the potential confusion/footgun is that you would not be modifying the original by doing this, you’re modifying a copy. Having to do the extra step of copying it into a var makes it clear that you are modifying a copy.
Here *i is capturing the pointer to a temporary which is why you get a *const Item, if you actually want to mutate the copy then you have to either leave out the capture by pointer or dereference the pointer, so either:
while (iter.next()) |*i| {
var tmp = i.*;
tmp.doSomething();
}
Or:
while (iter.next()) |i| {
var tmp = i;
tmp.doSomething();
}
Ahhhh, I see.
I didn’t realize that the |*i| was semantically different than manually taking a reference. So a pointer to a temporary is always const? That clears things up!
Thank you!
You both answered my question, but i’ll mark Sze’s as the solution because it provides more detail