Edit: TL;DR: Why is it OK for atomic variables that have not be declared with let mut to still be mutable?
The Question
After following some discussion on Internals, I came to the conclusion that either I'm misunderstanding something with regards to mutability in bindings and references, or there is some arguable (slight) inconsistency in the language.
The Rust book says:
In Rust, the compiler guarantees that when you state that a value won’t change, it really won’t change.
Hence one needs to declare a variable with let mut in order to modify it, or pass it as a mut reference. But atomic types (seem to) break this promise:
let v = 8u8; // This is immutable
let a = AtomicU8::new(8u8); // This isn't
foo(&mut v); // error, because immutable
a.store(9u8, Ordering::Relaxed); // but this is fine and dandy?!?
In fact, if you declare the atomic variable with let mut you even get a warning: variable does not need to be mutable , which furthers the confusion (at least mine).
(Disclaimer: I do know that AtomicU32::store takes &self, which is why it compiles.)
The Advanced View of Things
The explanation for this is that it's not really about mutability, but about shared vs. unique (quote below by @HadrienG, dtolnay::_02__reference_types - Rust):
However, that diction then doesn't explain why one needs let mut to assign or modify variable values, even if there is no sharing involved. IOW, if we rewrite it accordingly:
let v = 8u8; // no `let mut`
v = 9u8; // error, even though no sharing (but still understandable)
foo(&unique v); // error, even though no sharing (why?)
which moves the confusion to the previous "simple mutability explanation".
Suggestion
IMHO, to be really consistent, we'd need 3 reference kinds:
- immutable (current
&) - mutable unique (current
&mut) - mutable shared (what atomics do)
To illustrate, using &mut_shr for the new third kind of reference.
let iv = 8u32
let ia = AtomicU32::new(8u32);
read(&iv); // fine
modify(&mut iv); // ERROR: not mutable
// AtomicU32::store now takes `&mut_shr self`
ia.store(3u32, Ordering::Relaxed); // ERROR: not mutable (NEW!)
let mut mv = 8u32;
let mut ma = Atomic::new(8u32); // `let mut` now required for modifications
read(&mv); // still fine
modify(&mut mv); // fine
ma.store(3u32, Ordering::Relaxed); // now fine as well
Of course, the benefits of adding such a third reference type likely don't come close to the complications it'd created. OTOH, this should really only be required for very few library types, and can likely be restricted to the self parameter.
Question
I'm not really advocating we should make this change - I just would like to know whether this all makes sense, or am I really really misunderstanding something?