1
enum ManagedSlice<'a, T: 'a> {
    Borrowed(&'a T),
    Owned(T)
}

struct B<'a>{
    m: ManagedSlice<'a, Vec<u8>>
}

impl<'a> B<'a> {
    pub fn new() -> B<'a> {
        let m = ManagedSlice::Owned(vec![]);
        B{
            m: m
        }
    }
}

fn main() {
    let b = B::new();
}

Playground

I made this code to test if I really understand lifetimes. When I create let b = B::new() I get a B<'x> with a specific lifetime 'x. In theory this should be the lifetime of m. But as you see, m's life is very short, living only inside new.

What happens to a lifetime when something moves? Does m get the lifetime of B?

What about this modification:

enum ManagedSlice<'a, T: 'a> {
    Borrowed(&'a T),
    Owned(T)
}

struct B<'a>{
    m: ManagedSlice<'a, Vec<u8>>
}

impl<'a> B<'a> {
    pub fn new(s: &'a u8) -> B<'a> {
        let m = ManagedSlice::Owned(vec![]);
        B{
            m: m
        }
    }
}

fn main() {
    
    let b;
    {
        let n = 0;
        b = B::new(&n);
    }
}

Playground

now I'm forcing 'a to have the lifetime of n. I expected this to not compile since n has a short life and b gets the type B<lifetime of n>, but b has a lifetime greater than n.

What is happening?

Gatonito
  • 1,662
  • 5
  • 26
  • 55

2 Answers2

1

Lifetimes aren't variables. You can't make them equal anything, or extend them, or shorten them. They absolutely do not control how long things live.

They're just assertions (or, if you prefer, "claims") that we programmers make about our code:

fn new(s: &'a u8) -> B<'a>

Here you assert that, for each call to new, there's some lifetime 'a for which s will be borrowed and by which the returned B will be parameterised. So far, pretty useless.

Now, the interesting bit:

b = B::new(&n);

Rust knows the assertions you made about B::new, and here is a call to B::new—so it verifies that the assertions hold for this call...

What lifetime could 'a be in this case? Well the compiler knows that the function parameter s, which in this case is the argument &n, is borrowed for 'a; this constrains 'a to being no longer than the life of the borrow &n, which must clearly end before n is dropped upon going out of scope at the end of its block—but we then try to assign the result of the function to b, which exists for a longer lifetime. This violates the assertion we made about the lifetime of the function's return value, and Rust helpfully lets us know about that by failing to compile.

Finally, we only really talk about lifetimes in relation to borrows: when something is owned, it lives as long as it exists (indeed, notice your definition of ManagedSlice does not use its lifetime parameter in the Owned variant); when m is moved into the newly instantiated B, it lives for the life of that B. Furthermore, because m is a ManagedSlice::Owned, the only constraint on 'a is T: 'a, where in our case T is Vec<u8>: it means that 'a cannot outlive anything referenced by the Vec<u8> (such as its underlying heap allocation)—effectively we're asserting the trivially obvious, that the Vec must live at least as long as the B that owns it (but Rust requires this anyway, even if the assertion was not explicitly provided).

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • ` and Rust helpfully lets us know about that by failing to compile.` I don't get it. On my example, it compiles – Gatonito May 08 '21 at 20:26
  • @Gatonito: Ah, sorry, I’d missed that. It’s because Rust is smart enough to notice that `b` isn’t actually used after `n` goes out of scope, so it’s still able to prove the lifetime assertion. It’s a feature called “non-lexical lifetimes”. Read more [here](https://stackoverflow.com/a/50253558) – eggyal May 08 '21 at 20:59
0

What happens to a lifetime when something moves? A move represents a transfer of data from one memory location to another, rather than a reference to a memory location, which would have a lifetime associated with it. A moved value thus has a lifetime that lasts as long as the variable that holds it is in scope, and moving it will cause the lifetime of the new value to be the time that the new variable holding the value is in scope.

As for your second example, if you add a reference to b outside of the inner block, then you will get the compiler error you expect. The compiler is smart enough to know that even though b is defined in the scope above, it is not used outside of the inner scope, and thus the lifetime of b is equal to the lifetime of &n

transistor
  • 1,480
  • 1
  • 9
  • 12