More lifetimes misunderstandings

Why does this work? I have two snippets. First one I am returning a reference to a field of local variable. Why does the compiler deem this ok?


struct Test<'a>
{
    name : &'a str
}

impl<'a> Test<'a>
{
    fn name() -> &'a str
    {
        let b_t = Test{
            name : "harry potter"
        };
        return b_t.name;
    }
}

Here is a snippet that doesn't compile but adding a trait bound (commented in the code) causes it to compile. Of course the trait bound it suggested by the compiler but how am I supposed to understand it.

struct Test<'a>
{
    name : &'a str
}

impl<'a> Test<'a>
{
    fn name<'b>() -> &'b str
            // where 'a : 'b
    {
        let b_t : Box<Self> = Box::new(Test{
            name : "harry potter"
        });
        return b_t.name;
    }
}

In the first example, the function name() returns a immutable pointer to the static string "harry potter". This string will be stored somewhere in the static memory of your program by the compiler, hence the returned reference or pointer is valid for the whole lifetime of your program.
This gets a little bit clearer, if you add a function argument to name():

impl<'a> Test<'a>
{
    fn name(from_outside: &'a str) -> &'a str
    {
        let b_t = Test{
            name : from_outside,
        };
        return b_t.name;
    }
}

The return statement basically returns the function argument: return from_outside;

In your second example a separate lifetime 'b is introduced. The compiler can't know how long 'a and how long 'b will be valid. Since the function returns a string of the lifetime 'b, but your struct stores a string of the lifetime 'a, we must tell the compiler somehow under which "contracts" the code will be safe and sound. This is the case, if the lifetime of 'a is shorter then the lifetime of 'b. This is done by the lifetime bound where 'a: 'b. This tells the compiler that any Test that will be created, has a shorter lifetime than the &str it borrows from.

1 Like

In the first snippet, b_t has type Test<'static> since it holds a reference to a string constant. (String constants compile down to references into immutable global variables.) Then, since 'static is larger than 'a, returning the &'static str as an &'a str is allowed. (The lifetime can be shortened.) (You're returning a reference into the immutable global variable, not into a local variable, which is why it works.)

In the second example (ignore the box, it doesn't change anything), the issue is that you give b_t the type of Test<'a> rather than Test<'static> (since Self is a short-hand for Test<'a>.) Then, you try to return b_t.name as a &'b str, but since you have already explicitly shortened the 'static down to 'a, this only works if 'b is smaller than 'a. If you removed the Self type, then it would work because you could shorten directly from 'static down to 'b, instead of first shortening 'static to 'a, and then 'a to 'b.

3 Likes

Beautiful. Thank you.

Also note that in the first example, you are not returning "a reference to a field of a local variable". You are returning the value of the field itself, which happens to be a reference (of static lifetime). Ie., foo.bar is not the same as &foo.bar.

2 Likes