caio
caio

Reputation: 1989

Lifetimes when adding temporary values to vectors

I'm having some problems trying to understand how Rust lifetimes works in some situations, like below. I can't get it working but I'm not sure why.

struct Bar {
    value: &'static str,
}

struct Foo<'a, T: 'a> {
    bar: &'a T,
}

fn main() {
    let mut foos = Vec::new();

    let y = Bar {
        value: "Hello, world!",
    };
    let x = Foo { bar: &y };

    foos.push(x);
}
error[E0597]: `y` does not live long enough
  --> src/main.rs:15:25
   |
15 |     let x = Foo { bar: &y };
   |                         ^ borrowed value does not live long enough
...
18 | }
   | - `y` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

This is a simplified example of what I'm actually trying to achieve:

fn main() {
    let foos = vec![
        Foo { bar: &(Bar { value: "a" }) },
        Foo { bar: &(Bar { value: "b" }) },
    ];
}

I'd appreciate any ideas, thoughts or explanations.

Upvotes: 4

Views: 1082

Answers (1)

Shepmaster
Shepmaster

Reputation: 431599

After Non-Lexical Lifetimes

Both versions of your code now work, thanks to non-lexical lifetimes.

Before Non-Lexical Lifetimes

Your problem can be simplified to this example:

fn main() {
    let mut foos = Vec::new();
    let y = &42;
    foos.push(y);
}

The important thing to remember in this case is that variables are destroyed in the inverse order they are created. You could almost look at the code as

fn main() {
    let mut foos = Vec::new();
    {
        let y = &42;
        {
            foos.push(y);
        }
        // destroy y
    }}
    // destroy foos
}

For simple values like I am showing, this doesn't really matter, but it's more important when you have complicated types that have custom Drop implementations.

A simple fix is to reorder the statements:

fn main() {
    let y = &42;
    let mut foos = Vec::new();
    foos.push(y);
}

Now, it's guaranteed that the thing being referred to lives longer than the reference stored in the vector. For your original reduced example, this works:

let y = Bar { value: "Hello, world!" };
let x = Foo { bar: &y };
let mut foos = Vec::new();
foos.push(x);

Your original code has a bit trickier problem. Let's look at the expansion of the vec! macro:

let foos = <[_]>::into_vec(Box::new([Foo { bar: &(Bar { value: "a" }) }]));

Which we can simplify down to

let foos = Box::new(&42);

The problem is that the temporary variable is, well, temporary. It only exists for the duration of the function call. This means that the reference to the temporary variable can't last longer than that. That's why the error message suggests "consider using a let binding to increase its lifetime". By doing so, the variable will live longer than the function call.

Is it possible to make the temporary value last longer without using a let statement? The vector will have many values, say 30. So I'll have to place 30 let statements?

No, you have to be explicit about how long they should live, so you need to be explicit about where they are. I see two solutions:

  1. Change your structures so that they own items, instead of referring to them:

    struct Foo<T> {
        bar: T,
    }
    
    let foos = vec![
        Foo { bar: Bar { value: "a" } },
        Foo { bar: Bar { value: "b" } },
    ];
    
  2. Create a vector that owns all the inner types, then map over it to get references:

    let bars = vec![Bar { value: "a" }, Bar { value: "b" }];
    let foos: Vec<_> = bars.iter().map(|bar| Foo { bar: bar }).collect();
    

Upvotes: 1

Related Questions