My expectation was, that this was a compile error. Or that y is a copy of x and that this is a runtime error.
So my question is, what assumptions can I make, when reading const in the code? What guarantees does const give? It seems const makes it impossible to rebind a variable and also prevents mutating sub data directly. But there can still be some “far range” mutations? Or is this a bug?
A slice is a pointer + a length, so y is just a const pointer to the mutable x array. That is, y[0] = 42 would be an error, but x[0] = 42 is totally allowed.
Perhaps this behavior might make more sense with a manually created slice type:
// ~equivalent to `[]const i64` under the hood
const ConstSlice = struct {
ptr: [*]const i64, // pointer to some number of `i64`s
len: usize, // the number of `i64`s
};
pub fn main() !void {
var x: [2]i64 = .{ 0, 1 };
const y = ConstSlice{
.ptr = &x, // coerces to [*]const i64
.len = x.len,
};
assert(y.ptr[0] == 0);
evil(&x);
assert(y.ptr[0] == 42); // wait I thought it is const????
// ^ ask yourself what "it" is you're referring to here
}
You may be interested in the documentation on arrays and slices if you haven’t already seen it.
Also worth noting that if you want to make sure evil cannot mutate the slice, you can make the parameter a const slice:
Now this may seem a bit counter intuitive, but const does not mean that the underlying memory is constant. const only means, that you can’t change it through this variable.
For example if you see const slice/pointer in a function parameter, then the function is not allowed to change the underlying memory, but you as the caller can change it at any time.
Or if you get a const slice/pointer from a function, it means that you are not allowed to change it, but the data structure you got it from may change it.
So I would say that const is a useful tool for defining function contracts, but not so useful for ensuring that the memory never changes.
If you do want to ensure that the memory doesn’t change, then you need to make the source constant, in your example you could do:
const x: [2]i64 = .{0, 1};
If you have a non-trivial initialization, it’s also quite common to see patterns like this: