I guess this is because @constCast made a copy of the data and you just changed the copy. Add a std.debug.print("{any}\n", .{&foo}); after your print and you’ll see that &foo has the updated value.
Since the program gets the address of a constant, it must allocate its data in a read only area. If it did that foo.a = 2 should crash. (This is my opinion and obviously not the way that zig works).
I posted about this a while back; there are different opinions on whether this is a compiler bug or normal consequence of optimizations. Mutation after @constCast
fn MatchPtrType(comptime Parent: type, comptime Child: type) type {
return if (comptime isConstPtr(Parent)) *const Child else *Child;
}
// @constCast can propgate through optional pointers... ?*const T -> ?*T
pub fn last(self: anytype) ?MatchPtrType(@TypeOf(self), Node) {
if (comptime isConstPtr(@TypeOf(self))) {
return if (self.items.len > 0)
&self.items[self.items.len - 1] else null;
} else {
return @constCast(asConst(@This(), self).last());
}
}
The pattern that works is:
non-const x -> *const x -> @constCast(ptr)
You do not want to go in this direction:
const x -> *const x -> @constCast(ptr)
The reason being is that the compiler has made assumptions about x because it begins as const. You’re invalidating those assumptions. If you start with non-const, go up to const, and then back down, everything is fine.
I think the answer is that this is just UB. With @constCast the programmer asserted that the “thing” being pointed to by the pointer can be written, but then this was not true and so optimizations like what dimdin mentioned become observable (and wrong) etc.