I run into various cases where I encounter the error
cast discards const qualifier
In most cases I usually need to change from var to const or vice versa.
But I am not sure I actually understand what the error message really mean because most of the time I am not performing explicit cast, and the line of code the compiler points at in the error, is usually not one that has a cast also.
Would be nice to actually know what the compiler means when it errors with “cast discards const qualifier”.
Hi, it sounds like you almost understand what’s going on, actually. There’s just an implicit cast or type coercion going on somewhere; this implicit cast might be fine and convenient for reading the const’s value, but not for mutating it.
The key to reading the debug message is to look at which types it says it found, and then look at the arrows in the excerpted code.
src/main.zig:10:20: error: expected type '[]u8', found '*const [7:0]u8'
try changePath("huh.txt");
^~~~~~~~~
src/main.zig:10:20: note: cast discards const qualifier
src/main.zig:5:25: note: parameter type declared here
pub fn changePath(path: []u8) !void {
If you imagine the arrows being a box where your bytes are, it’s telling you that only a certain type fits in that box, after all the explicit casting and coercion rules have been applied. You have to change the type it expects, or what you are giving it.
PS: to answer your question more directly, it’s telling you it tried to erase the const (coercion/discard qualifier) so you could pass that to the function’s signature, since the types are almost compatible for reading. But then something mutates it in that scope or even nested deeper, and the compiler has to abandon that assumption of const.
Now to original version.
As I understand in this case error is because buf is immutable,
but we cast it’s address to some pointer and we can potentially change buf content via this pointer and
constCast() makes the pointer to be pointer to immutable data
making buf mutable… well, just makes it mutable, so the pointer is ok.
Function parameters can be pointers (memory locations)
Function parameters can be value (contents of memory)
You can have pointers to constant memory.
The compiler will check to make sure you do not try to use constant memory where you should be using modifiable memory (illegal cast of const to var)
The concept of constant memory enables a lot of optimizations (lets you re-use memory because you know you arent going to modify it!)
//! Run this file using: zig test path_to_this_file.zig
//!
//! We expect the first test to succeed and the second test to produce the const cast compile error.
const std = @import("std");
// All function parameters are constant in zig.
/// add one to the number I pass into this function by reference.
/// The reference (location in memory) is constant. But the contents of the memory
/// at that location can change.
pub fn addOneInPlace(num: *u8) void {
num.* += 1;
}
/// The number I am passing into this function is constant and a new number is returned.
pub fn addOne(num: u8) u8 {
return num + 1;
}
/// The location in memory is constant and the contents of the memory is constant.
pub fn addOneUsingReference(num: *const u8) u8 {
return num.* + 1;
}
test "add one correctly" {
var modifiable_number: u8 = 1;
addOneInPlace(&modifiable_number);
try std.testing.expect(modifiable_number == 2);
const constant_number: u8 = 1;
const new_constant_number: u8 = addOne(constant_number);
try std.testing.expect(new_constant_number == 2);
const new_constant_number_again = addOneUsingReference(&constant_number);
try std.testing.expect(new_constant_number_again == 2);
}
test "add one illegally" {
const constant_number: u8 = 1;
addOneInPlace(&constant_number); // compile error here!
try std.testing.expect(constant_number == 1);
}
jeff@jeff-debian:~/repos/zecm$ zig test experimental/test.zig
experimental/test.zig:40:19: error: expected type '*u8', found '*const u8'
addOneInPlace(&constant_number); // compile error here!
^~~~~~~~~~~~~~~~
experimental/test.zig:40:19: note: cast discards const qualifier
experimental/test.zig:11:27: note: parameter type declared here
pub fn addOneInPlace(num: *u8) void {
^~~
That’s undefined behavior (illegal behavior in Zig lingo). In this case, the compiler suggestion should not be followed. If the underlying buffer is const you are never allowed to change it, no matter how many casts and builtins you throw at it.
Behind the scenes, const data can go into the read-only section, which is protected from change by the OS, or it can be baked directly into the code, so it would have no address. @constCast can ony be used if the pointed-to memory is mutable, but you ended up with a const pointer to it.
This works because buffer is actually mutable, despite what the pointer says.
Obviously, no one should write code like this. But sometimes you have a callback system or some kind of channel, and it expects a const pointer. So you can give it a const pointer and, at the other end, since you are sure you passed a pointer to mutable data, you can cast away the constness.
No @constCasting has no effect on the actual mutability of a buffer.
It only allows you to get a mutable pointer and using that mutable pointer on a read only buffer to attempt to change something is illegal and can result in a panic / crash.
It can happen to work, but it still isn’t reliable because the data of the const buf can be placed into read only memory by the compiler and if that happens the program will crash. That is why you shouldn’t use @constCast to remove the const unless you actually know that the buf was declared with var.
@constCast should only be used when it is unavoidable, here it is clearly avoidable and when you know for certain that the buffer will always be mutable.
Using a const buf with @constCast will likely cause unexpected panics when the compiler changes and places more immutable things in read-only memory.
It also can cause logic bugs / undefined behavior because one part of your program claims the memory is immutable while another modifies it.
I guess maybe I misunderstood you a bit, if you meant to use a mutable pointer, without actually modifying anything, but if you aren’t modifying it, then it is better to just use a const pointer instead of casting away the constness and hoping nobody will actually use the mutable pointer for setting new values.
Passing around non const pointers to constant data is a bad practice and only makes sense in very rare corner cases, where you are sure that this particular instance won’t be modified while maybe other ones will be.
But people who work on such corner cases will be aware of those details.
Overall telling the compiler this data is constant/immutable and then changing it is a bad idea.
No, this is exactly what was needed, immutable pointer to immutable data.
It was that note: use @constCast to discard const qualifier that disoriented me.
maybe there should be two notes where the first is: note: use a constant slice/pointer or note: use @constCast to discard const qualifier
Might make sense to think about the different cases and how the error message could be improved in the compiler, I don’t think the current note is that good, it is quite rare where discarding const is actually the right thing to do.
Just to be super clear: it’s a “note”, not a “hint”. So it’s not really a suggestion or advice, it’s just informing the programmer the existence of a relevant language feature.
I’ve been very strict rejecting contributions that add “hints” to the compiler, because in my experience such hints can never be 100% accurate (otherwise they wouldn’t need to be diagnostics, the language would just allow the construct), and so hints are not allowed in compiler diagnostics. They must inform rather than prescribe. Similarly, I think this is what good documentation comments do.
Yes, this may be somewhat confusing.
But now I understand what compiler complains about for my example with pointer casting at least. Sorry for repetition Here is context: