When trying to write to a const array rustc does compile the code without throwing an error but does not assign the value to the array.
As an example
const FOO : [i32;1] = [0];
fn main() {
FOO[0] = 1;
println!("{:?}",FOO);
}
compiles and prints
[0]
Is this behavior intended or should the compiler stop with an error as it does when compiling the following code?
const BAR : i32 = 0;
fn main() {
BAR = 1;
println!("{:?}",BAR);
}
BAR = 1;
^^^^^^^ left-hand of expression not valid
but does not assign the value to the array.
"of course" not, you can't change constants, as they are (by definition) constant. If we changed that, we'd have a lot of problems (think about what something like 1 = 42; means)
What's going on here is totally not intuitive. Your code essentially expands to
const FOO : [i32;1] = [0];
fn main() {
let mut tmp = FOO;
tmp[0] = 1;
println!("{:?}",FOO);
}
which also doesn't produce any diagnostics.
Maybe we could have a lint which tells you about "unused" variables like this, even if these variables are temporaries.
This is ridiculous that such code even compiles:
struct Thing(usize);
impl Thing {
fn set(&mut self, val: usize) {
self.0 = val;
}
}
const CONST_THING: Thing = Thing(0);
fn main() {
println!("Before: {}", CONST_THING.0);
CONST_THING.set(1);
println!("After: {}", CONST_THING.0);
}
I just spent more than an hour figuring out what was wrong with my code because I accidentally defined one of my values as const while trying to mutate it and Rust didn't even give a warning when it would throw an error if it was a static or a let binding. That just makes no sense whatsoever.
It turns out that there is a legitimate usage of this pattern with thread_local:
When you use the thread_local! macro, a new const gets defined:
A LocalKey stores a function pointer, so it's fine for it to get copied at each call site:
If this kind of code is very uncommon (which I suspect it is), we could probably get away with whitelisting LocalKey in any lint that we write. However, if there are any crates defining this kind of const, then downstream crates will get a false-positive warning of every single use of the const (even if the fact that it's a const is an implementation detail, like with thread_local!).
Another challenge is interior mutability: this code is probably fine:
const EMPTY_VEC: Vec<u8> = Vec::new();
fn main() {
EMPTY_VEC.len();
}
but this code isn't:
use std::cell::Cell;
struct Foo {
inner: Cell<u8>
}
impl Foo {
fn sneaky_len(&self) -> u8 {
self.inner.replace(0)
}
}
const FOO: Foo = Foo { inner: Cell::new(25) };
fn main() {
assert_eq!(FOO.sneaky_len(), 25);
assert_eq!(FOO.sneaky_len(), 25);
let foo = FOO;
assert_eq!(foo.sneaky_len(), 25);
assert_eq!(foo.sneaky_len(), 0);
}
Most helpful comment
This is ridiculous that such code even compiles:
I just spent more than an hour figuring out what was wrong with my code because I accidentally defined one of my values as
constwhile trying to mutate it and Rust didn't even give a warning when it would throw an error if it was astaticor aletbinding. That just makes no sense whatsoever.