The previous posts explain the motivation and reasoning behind Rust's design (which is indeed very interesting). But really, one must understand that, due to that motivation ("shared mutable state is bad") Rust offers a design with very specific rules, and so these rules must be followed, even if they may be overly restrictive in some cases. The rule here is the following:
-
& _
is a shared reference: the pointer can be copied around, so it is perfectly valid to have multiple such pointers pointing to the same value (we say the pointers are aliased). Such pointers / references are the ones used by "all" other languages.
-
&mut _
is a "Rust invention" / specific to Rust (and maybe to some other language; but the truth is most other languages do not have such a construct / reference): it is an exclusive / unique / unaliased reference / pointer. It could have been named &uniq
.
By very definition, by construction, there cannot be other potentially active / usable pointers pointing to the same value.
Back to your example, the other design choice of Rust is to make mutation, "by default", happen through these exclusive &uniq
references (that's why such references are named mut
; but, imho, that was a mistake, as this (recurring) thread proves).
When you have:
let mut x = 42;
let x_ref = &x;
x = 13;
println!("x_ref = {}", x_ref);
you are doing:
-
let mut x ...
You say that x
is a value you may want to take exclusive references to (the main purpose of doing that being to mutate it in the default fashion, hence it being named mut
).
-
let x_ref = &x;
You get a shared reference / "pointer" to x
, which is held (and thus usable) up until the last time it is used:
println!("x_ref = {}", *x_ref); /* read-deref */
-
x = 13
This is using the default mechanism to mutate x
: that of going through a &mut
exclusive reference. In other words, that line is sugar for:
*(&mut x) = 13;
And there lies the problem: we are attempting to get an exclusive reference / pointer to x
, despite there being another active / usable pointer around (x_ref
). Hence the error.
Another way of seeing this is to imagine Rust's &
/ &mut
system as a compile-time RwLock
(multiple readers, single writer) that throws a compile-error whenever the code would deadlock:
let x_ref = &x; // acquire a read lock
*(
&mut x // try to acquire a write / exclusive lock
) = 13;
println!("x_ref = {}", x_ref); // last usage of the read lock;
// the lock is only released afterwards
Now, you may have noticed that I have insisted on x = 13
being the default way to mutate a value, which hints at there being other ways to do it.
Indeed, if C / C++, for instance, which do not posess that &uniq
reference abstraction, is able to do:
int x = 42;
int * x_ref = &x;
x = 13;
printf("x_ref = %d", *x_ref);
then Rust ought to also be able to express that, right? And indeed, it does; it's just that you won't be able to use the x = 13;
mutation sugar, or the *x_ref
read-dereference sugar, since those are reserved to the idiomatic by-exclusive-reference-mutation and by-shared-reference -read patterns respectively. Instead one must use more explicit constructs, mainly that of single-threaded by-value mutation, which is what the above C/C++ code is relying on:
// wrapper for single-threaded by-value mutation
use ::std::cell::Cell as SingleThreadedMutable;
let x = SingleThreadedMutable::new(42);
// shared reference (same as in C)
let x_ref = &x;
// by-value mutation through another shared reference (safe because thread-local)
x.set(13); /* sugar for (&x).set(13) */
println!("x_ref = {}", x_ref.get()); // outputs 13