I want to understand lifetimes and the elision rules better.
Suppose we want to extend a Vec<i32>
(really Vec<&i32>
)
fn extend_vector(
v: &mut Vec<&i32>,
x: &i32,
) {
v.push(x); // error: lifetime may not life long enough
}
And the main method is:
fn main() {
let mut v: Vec<&i32> = vec![];
let x = 42;
let y = 69;
extend_vector(&mut v, &x);
extend_vector(&mut v, &y);
println!("{v:?}");
}
The borrow checker complains that "lifetime may not live long enough".
The solution is to introduce another lifetime:
fn extend_vector<'a, 'b>(
v: &'a mut Vec<&'b i32>,
x: &'b i32
) {
v.push(x);
}
According my understanding the println
macro does an immutable borrow of v
, so the lifetime of &x
needs to be extended to the whole main
function. Now &mut v
and &x
have the same lifetime. But then the second extend_vector
call cannot borrow &mut v
a second time.
My question is regarding the nature of the borrow that println
does. Does it matter that the borrow println
does is immutable? Is it in any way related to the error?
Rust elision rules expand your extend_vector
function as such:
fn extend_vector<'a, 'b, 'c>(
v: &'a mut Vec<&'b i32>,
x: &'c i32,
)
where
'b: 'a, // `'b` lives longer or equal to `'a`
{
// Error: lifetime may not live long enough
// as you try to store a `'c` into a vec that needs `'b`
v.push(x);
}
Without returning any references, Rust gives each reference it's own lifetime. Also references to references are given a relationship.
To fix this, follow the compilers suggestion to give the i32
s the same lifetime annotation:
// Note there is still lifetime elision,
// `v` is given an elided annotation of `'0` by the compiler.
fn extend_vector<'a>(
v: &mut Vec<&'a i32>,
x: &'a i32,
) {
v.push(x);
}
fn main(){
let mut v: Vec<&i32> = vec![];
let x = 42;
let y = 69;
// Exclusive borrow of `v`. Shared borrow of `x`.
extend_vector(&mut v, &x);
// Borrow of `v` ended, as it does not return a reference to `v`.
// Borrow of `x` not ended, as `v` is storing a reference to it,
// Note that an exclusive borrow of `x` below would fail
// as it is already borrowed as shared.
// 100% fine to call this again.
extend_vector(&mut v, &y);
println!("{v:?}");
}
Your understanding of lifetime elision and lifetimes of variables is incorrect. println
is unrelated to the errors. I believe you thought Rust would give each reference the same lifetime annotation, as per something like C#. Lifetimes are never "extended" how you said. v
is never borrowed multiple times at once.
If you are using Rust Analyzer, which I highly recommend, you can set rust-analyzer.inlayHints.lifetimeElisionHints.enable
to "always"
to see elided lifetimes in function signatures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With