Mutable References and Containers
Mutable References work exactly the same way as regular references, with regards to lifetime elision. The reason we have a chapter about them, however, is that if you have a mutable reference, you might need to tell the compiler about lifetimes even without a return value.
For example, let's take a look at this example:
fn insert_value(my_vec: &mut Vec<&i32>, value: &i32) {
my_vec.push(value);
}
We're not returning anything; so lifetimes don't matter, right?
Unfortunately, lifetimes are still important. The reference value
actually needs to
live for the same time as the contents of the vector. If they didn't,
the vector might contain an invalid reference. For example, what would happen
in this scenario?
fn insert_value(my_vec: &mut Vec<&i32>, value: &i32) {
my_vec.push(value);
}
fn main() {
let x = 1;
let my_vec = vec![&x];
{
let y = 2;
insert_value(&mut my_vec, &y);
}
println!("{my_vec:?}");
}
The reference to y
in the above example is dangling when we try to print the vector!
We can use lifetimes to ensure that the two references live for the same amount of time:
fn insert_value<'vec_lifetime, 'contents_lifetime>(my_vec: &'vec_lifetime mut Vec<&'contents_lifetime i32>, value: &'contents_lifetime i32) { my_vec.push(value) } fn main(){ let mut my_vec = vec![]; let val1 = 1; let val2 = 2; insert_value(&mut my_vec, &val1); insert_value(&mut my_vec, &val2); println!("{my_vec:?}"); }
This signature indicates that there are two lifetimes:
'vec_lifetime
: The vector we've passed the function will need to live for a certain period of time.'contents_lifetime
: The contents of the vector need to live for a certain period of time. Importantly, the newvalue
we're inserting needs to live for just as long as the contents of the vector. If they didn't, you would end up with a vector that contains an invalid reference.
Do We Even Need Two Lifetimes?
You might wonder what happens if we don't provide two lifetimes. Does just one lifetime work?
fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
my_vec.push(value)
}
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1);
insert_value(&mut my_vec, &val2);
println!("{my_vec:?}");
}
No, it doesn't. We get two errors. Let's look at the first one:
error[E0499]: cannot borrow `my_vec` as mutable more than once at a time
--> /tmp/rust.rs:11:18
|
10 | insert_value(&mut my_vec, &val1);
| ----------- first mutable borrow occurs here
11 | insert_value(&mut my_vec, &val2);
| ^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
This seems strange -- why can't you borrow my_vec
?
Well, let's walk through what the compiler sees:
&val
needs to last for as long as my_vec
exists:
fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
my_vec.push(value)
}
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1); // \
insert_value(&mut my_vec, &val2); // | - &val1 needs to last this long.
// |
println!("{my_vec:?}"); // /
}
Whereas &mut my_vec
only needs to last for the duration of insert_value
.
fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
my_vec.push(value)
}
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1); // <- &mut my_vec only needs to last this long.
insert_value(&mut my_vec, &val2);
println!("{my_vec:?}");
}
But, we've told the compiler that it needs the borrows of both &val1
and
&mut my_vec
to share the same lifetime. So the compiler extends the borrow
of &mut my_vec
to ensure they do share a lifetime:
It sees that if it let &mut my_vec
live as long as &val1
, it would
have that single region of code:
fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
my_vec.push(value)
}
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1); // \
insert_value(&mut my_vec, &val2); // | - 'one_lifetime must be this region of code.
// |
println!("{my_vec:?}"); // /
}
And that's fine. But now the compiler gets to the next line, and it sees you're
trying to borrow &mut my_vec
again.
The compiler already decided &mut my_vec
has to exist until the end of the function.
So now, you're asking it to create two mutable references... and that's not allowed.
So the compiler throws an error -- you're not allowed to borrow &mut my_vec
again.
Why does having two lifetimes fix this error?
Have a think before reading this section -- why does having two lifetimes solve this bug?
Before, the compiler had to decide that &mut my_vec
and &val1
shared a lifetime.
In other words, that they lived as long as each-other.
By using two lifetimes, we've told the compiler that &mut my_vec
and &val1
don't necessarily have to live for the same amount of time. And so,
it finds the following lifetimes:
fn insert_value<'vec_lifetime, 'contents_lifetime>(my_vec: &'vec_lifetime mut Vec<&'contents_lifetime i32>, value: &'contents_lifetime i32) {
my_vec.push(value)
}
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1); // <- 'vec_lifetime \
insert_value(&mut my_vec, &val2); // | 'contents_lifetime
// |
println!("{my_vec:?}"); // /
}
Exercise Part 1: The Other Error
First, let's look at the other error we got in the last section:
error[E0502]: cannot borrow `my_vec` as immutable because it is also borrowed as mutable
--> /tmp/rust.rs:13:16
|
10 | insert_value(&mut my_vec, &val1);
| ----------- mutable borrow occurs here
...
13 | println!("{my_vec:?}");
| ^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
|
Can you explain why this error occurs? Write it out in 50 words or less.
Exercise Part 2: Writing Our Own
Add appropriate lifetimes to the function in the exercise.