Search code examples
rustdereferencelifetime

Understanding lifetimes for parameterized structs in Rust


The following code

struct Cat<'a, T> {
    coolness: &'a T,
}

complains saying

error[E0309]: the parameter type `T` may not live long enough
 --> src/main.rs:2:5
  |
1 | struct Cat<'a, T> {
  |                - help: consider adding an explicit lifetime bound `T: 'a`...
2 |     coolness: &'a T,
  |     ^^^^^^^^^^^^^^^
  |
note: ...so that the reference type `&'a T` does not outlive the data it points at
 --> src/main.rs:2:5
  |
2 |     coolness: &'a T,
  |     ^^^^^^^^^^^^^^^

With an explicit lifetime bound, it compiles. When I instantiate the struct where T is an &i32 and despite each reference having a different lifetime, the code compiles. My understanding is that the compiler sees that the inner & outlives the outer &:

struct Cat<'a, T>
where
    T: 'a,
{
    coolness: &'a T,
}

fn main() {
    let b = 10;
    {
        let c = &b;
        {
            let fluffy = Cat { coolness: &c };
        }
    }
}

Does Cat { coolness: &c } expand to Cat { coolness: &'a &'a i32 }? Does the inner reference also assume the same lifetime and so forth for more nested references?


Solution

  • Does Cat { coolness: &c } expand to Cat { coolness: &'a &'a i32 }?

    Yes, the Cat ends up with a reference to a reference. This can be demonstrated by the following code compiling:

    let fluffy = Cat { coolness: &c };
    fn is_it_a_double_ref(_x: &Cat<&i32>) {}
    is_it_a_double_ref(&fluffy);
    

    However, the lifetime on each reference is not necessarily the same.

    My understanding is that the compiler sees that the inner & outlives the outer &

    That's right. And this is precisely where the T: 'a bound comes into play.

    Lifetime bounds are a bit tricky to understand at first. They put restrictions on the references contained in T. For example, given the bound T: 'static, types that don't contain any references, or only contain 'static references, e.g. i32 and &'static str, satisfy the bound, while types that contain non-'static references, e.g. &'a i32, don't, because 'a: 'static is false. More generally, given the bound T: 'a, the type T satisfies the bound if, for every lifetime parameter 'x on T, 'x: 'a is true (types with no lifetime parameters trivially satisfy the bound).

    Back to your code now. Let's give some names to these references. We'll say the type of coolness is &'fluffy &'c i32. 'c is the lifetime of the variable c and 'fluffy is the lifetime of the variable fluffy (counterintuitively, lifetimes encode the scope of a borrow, not the lifetime of the referent, although the compiler does check that the borrow doesn't extend beyond the referent's lifetime). That means the type of Fluffy is Cat<'fluffy, &'c i32>. Is it true that &'c i32: 'fluffy?

    To check if &'c i32: 'fluffy is true, we need to check if 'c: 'fluffy is true. 'c: 'fluffy is true because the variable c goes out of scope after fluffy.