Mutable References
Owned values can be mutated, and immutable references to those values cannot.
However, there is a type of reference which does allow mutation: a mutable reference. We can get one of those like so:
let mutable_years: &mut Vec<i64> = &mut years;
mutable_years.clear(); // clear() removes all elements from the Vec
The type of mutable_years
is &mut Vec<i64>
.
Note that this is not a let mut
, but rather a plain old let
. Because of
that, we cannot reassign mutable_years
to something else - that is, we can't
write mutable_years = ...
or else we'll get a compile error - but since
mutable_years
is itself a mutable reference, we can pass it to functions
which expect &mut Vec<i64>
values.
As it happens, the .clear()
method on Vec
expects a mutable reference.
It's defined like so:
fn clear(&mut self)
Mutable references are strictly more powerful than immutable references, in that you can pass a mutable reference to any function which expects an immutable one, but not the other way around.
For example, you can call .clear()
on a &mut Vec<i64>
, and you can also
call .len()
on it...but if you have a &Vec<i64>
, you can call .len()
in it but not .clear()
.
Mutable reference restrictions
One downside of mutable references compared to immutable ones is that as long as you have a mutable borrow active on a value, you are not allowed to have any other borrows (mutable or immutable) active on that value.
For example, this is totally allowed:
let years = vec![1995, 2000, 2005];
let years_ref1 = &years;
let years_ref2 = &years;
...however, this would not compile:
let years = vec![1995, 2000, 2005];
let years_ref1 = &mut years;
let years_ref2 = &mut years; // ERROR: tried to borrow while a mutable borrow was active!
...and neither would this:
let years = vec![1995, 2000, 2005];
let years_ref1 = &mut years;
let years_ref2 = &years; // ERROR: tried to borrow while a mutable borrow was active!
In practice, this restriction rarely comes up except when doing concurrency in Rust - and when doing concurrency, it's very important.
We won't get into Rust concurrency in this workshop, but briefly: there's a category of errors called "data races" where two different threads have access to mutate a value at the same time, and the result can be really nasty bugs. The "only one at a time" restriction means that Rust can rule out data races at compile time. This is one of Rust's major selling points to anyone who wants to write high-performance concurrent systems, since avoiding data races is critically important in that domain.