0

I'm new to Rust, and I'm trying to learn more about lifetimes. I came across the following piece of code, which won't compile (Playground):

struct Person<'a, 'b> {
    name: &'a str,
    email: &'b str, 
}

fn main() {
    let my_name: String = String::from("John Doe");
    let me: Person;

    {
        let my_email: String = String::from("[email protected]");

        me = Person {
            name:  &my_name,
            email: &my_email,
        };
    }

    println!("My name is {}.", me.name);
}

Notice the use of two different lifetime parameters in the definition of Person.

Here is the compilation error:

error[E0597]: `my_email` does not live long enough
  --> src/main.rs:15:20
   |
15 |             email: &my_email,
   |                    ^^^^^^^^^ borrowed value does not live long enough
16 |         };
17 |     }
   |     - `my_email` dropped here while still borrowed
18 |
19 |     println!("My name is {}.", me.name);
   |                                ------- borrow later used here

The compiler complains about my_email not living long enough. However, I'm not using it, or any reference to it (me.email, for example). I'm rather trying to print me.name, which refers to my_name, and the lifetime of it won't end until the end of main function.

However, the following piece of code, which is a small modification of the code above, compiles and runs as intended (Playground):

struct Person<'a, 'b> {
    name: &'a str,
    email: &'b str, 
}

fn main() {
    let my_name: String = String::from("John Doe");
    let name_ref: &str;

    let me: Person;
    {
        let my_email: String = String::from("[email protected]");

        me = Person {
            name:  &my_name,
            email: &my_email,
        };

        name_ref = me.name;
    }

    println!("My name is {}.", name_ref); // Prints "My name is John Doe."
}

I can't understand why the first piece of code doesn't work, despite everything looking fine. Can someone explain what's going on?

1 Answer 1

6

You are using it. You are storing it in the struct.

As long as me exists, the me.email exists as well, regardless of whether you are accessing it or not. And according to Rust's soundness rules, a reference that exists has to always be outlived by the data it points to. A reference cannot be dangling.

The reason the second code works is because you do not access the me structure outside of the innermost context. name_ref no longer depends on my_email, and me no longer exists. So Rust is able to destroy me first and can then safely destroy my_email.

One subtle detail to understand here is that name_ref does not depend on me. Assigning a reference to a new variable (in this case, from me.name to name_ref) copies the reference. name_ref purely depends on my_name.

How does the compiler know that? By implicit lifetimes. The Rust compiler actually keeps a hidden lifetime annotation with every reference (at compile time only). So the Rust compiler understands that name_ref actually references my_name (or at least in a more abstract way that the lifetime of the thing name_ref points to has to match my_name).


structs in Rust take over the lifetime of their members. So if you store a reference inside of the struct, the entire struct will depend on the referenced object's lifetime. You could say the struct derives its lifetime dependencies from its members. That's also what the compiler warning tells you: because you use me in general, this lifetime dependency has to be honored. It doesn't matter if you use the actual object.

You can see this already in its declaration:

struct Person<'a, 'b> {...}

The lifetime 'b, in this case, captures the dependency to the my_email object, and the lifetime is attached to the entire Person object, not just its email member.

This can go even further:

struct Person<'a> {
    email: Option<&'a str>,
}

fn main() {
    let mut me: Person;
    {
        let my_email: String = String::from("[email protected]");

        me = Person {
            email: Some(&my_email),
        };

        me.email = None;
    }

    println!("My name is {:?}.", me.email);
}
error[E0597]: `my_email` does not live long enough
  --> src/main.rs:11:25
   |
11 |             email: Some(&my_email),
   |                         ^^^^^^^^^ borrowed value does not live long enough
...
15 |     }
   |     - `my_email` dropped here while still borrowed
16 |
17 |     println!("My name is {:?}.", me.email);
   |                                  -------- borrow later used here

Note that at the point where the inner context is left, me.email contains None, so the me object isn't even referencing my_email any more. Nonetheless, creating the object with Person { email: Some(&my_email) } burns this lifetime into the instantiated type of me. A lifetime behaves similar to a generic: it is burned into the type of the variable, and the type of the variable cannot change by mutating it.

Not the answer you're looking for? Browse other questions tagged or ask your own question.