Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding how Rust elides lifetimes with mutable references

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?

like image 412
Markus Klyver Avatar asked Oct 15 '25 03:10

Markus Klyver


1 Answers

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 i32s 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);
}

Further

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.

like image 105
RedRam Avatar answered Oct 16 '25 20:10

RedRam