Jacob Brown
Jacob Brown

Reputation: 7561

Why can I use the same lifetime label for variables that have different lifetimes?

Why does this code compile?

#[derive(Debug)]
pub struct Foo<'a> {
    pub x: &'a [&'a str],
}

fn main() {
    let s = "s".to_string();
    let t: &str = s.as_ref();
    {
        let v = vec![t];
        let foo = Foo { x: &v[..] };
        println!("{:?}", &foo);
    }
}

In my Foo struct, my x field contains a slice of &strs. My understanding of these lifetime labels is that the slice and the individual &strs have the same lifetime.

However, in my example the &str t (in the outer block) does not have the same lifetime as the container slice (in the inner block).

Upvotes: 1

Views: 277

Answers (2)

Matthieu M.
Matthieu M.

Reputation: 300099

The syntax 'a is actually used in two different situations:

  • labeling a loop
  • indicating a bound

The first situation, labeling a loop:

fn main() {
    'a: loop {
        println!("{}", 3);
        break 'a;
    }
}

Here, 'a clearly delineates the lifetime of the loop body, and allows breaking from multiple layers of loops in one fell swoop.

The second, and more similar situation, is using 'a to represent a bound:

fn<'a> susbtr(haystack: &'a str, offset: usize) -> &'a str;

In this case, the lifetime 'a does not represent the actual lifetime of the variable, it represents a bound on the lifetime of the referenced variable and allows tying together the bounds of various variables.

Note that the caller and callee interpret the bound differently:

  • from the perspective of the caller, 'a is an upper-bound, a promise that the return value will live at least as long as the parameter (maybe longer, no guarantee)
  • from the perspective of the callee (ie, substr), 'a is a lower-bound, a check that any returned value must live at least as long as the parameter (maybe longer, not necessary)

We can have variance since the bound does not represent the actual lifetime, when a single bound is used for multiple lifetimes, the compiler will simply deduce the lowest/highest bound that makes sense for the situation:

  • the caller gets the lowest upper-bound feasible (ie, the least guarantee)
  • the caller gets the highest lower-bound feasible (ie, the least constraint)

For example:

fn<'b> either(one: &'b str, two: &'b str, flag: bool) -> &'b str {
    if flag { one } else { two }
}

can be called with:

fn<'a> call(o: &'a str, flag: bool) -> &'a str {
    either(o, "Hello, World", flag)
}

Here, the lifetime of o is unknown (some 'a) whilst the lifetime of "Hello, World" is known ('static), 'static is by definition the greater of the lifetimes (it lives for all the program).

  • the caller of call only knows that the return value lives at least as long as o
  • call must guarantee this, it supplies o and "Hello, World" to either where 'b is deduced to be the lowest bound between 'a and 'static (thus 'a)
  • either simply must return something that lives as long as either one of its arguments; it's unaware that their lifetime may differ, and cares not

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 431599

My understanding of these lifetime labels is that the slice and the individual &strs have the same lifetime.

This is a common misconception. It really means that there has to be a lifetime that applies to both. When instantiated, the Foo struct's 'a corresponds to the lines of the inner block after let v = vec![t]; as that is a lifetime that both variables share.

If this flexibility didn't exist, lifetimes would be super painful to use. Variables defined on two lines have different actual lifetimes (the one defined first outlives the one defined second). If the lifetimes had to actually match, we'd always have to define all the variables on the same line!

Some more detailed information is available in RFC #738 — Variance.

Upvotes: 7

Related Questions