-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Zig 0.6.0 (not master). This is related to, actually maybe a subset of, #4021 / #3696 (this issue doesn't involve result copy elision).
I understand that this was intended to be a feature of zig: args passed as values "may" be silently translated to pass-by-reference by the compiler. I think the intent was to stop the user from passing const pointers as an "optimization". But it's also a footgun, sort of like #2915. The problem occurs when you have another non-const pointer aliasing the same memory as the argument value.
const std = @import("std");
const Thing = struct {
value: u32,
};
const State = struct {
thing: Thing,
};
fn inner(state: *State, thing: Thing) void {
std.debug.warn("before: {}\n", .{thing.value}); // prints 10
state.thing.value = 0;
std.debug.warn("after: {}\n", .{thing.value}); // prints 0
}
pub fn main() void {
var state: State = .{
.thing = .{ .value = 10 },
};
inner(&state, state.thing);
}The behavior here depends on the compiler implementation. It seems that right now, if thing is a struct value, it's passed by reference. But if it's a bare u32, it's passed by value. I don't know if it will always be this simple (I assume there are plans to pass "small" structs by value.)
The workaround for this situation is to make an explicit copy using a redundant-seeming optimization, probably accompanied by a comment explaining what's going on. Or else to restructure the code at a higher level, but then this footgun will still be lurking in the shadows.
I think that any optimistic "assume no aliases" optimization ought to be opt-in rather than opt-out. That would mean, either go back to the C way of things, or add a new syntax (some symbol that means "compiler can choose between value and const pointer"). Either way, a plain argument should always be passed by value. What do others think?