Helin Wang
Helin Wang

Reputation: 4202

Does a generic lifetime materialize as the reference's lifetime or the referenced value's lifetime?

Consider the following program:

fn main() {
    let c;                                      |<-'a
    let mut a = "Hello, world!".to_string();    |
    {                                           |
        let b = &mut a;           |<-'b         |
        c = foo(b);               |             |
    }                                           |
    println!("{}", c)                           |
}

fn foo<'z>(a: &'z mut str) -> &'z str {
    a
}

b's lifetime is 'b, but c's lifetime is 'a, which is longer than 'b. foo's lifetime constraint says foo's return value (c in this case) should have the same lifetime with its argument (b in this case). How is foo's lifetime constraint satisfied?

However, this program compiles, so I guess foo's lifetime parameter 'z materializes as b's referenced value (a)'s lifetime so that foo's lifetime constraint is satisfied?

Upvotes: 3

Views: 133

Answers (1)

Shepmaster
Shepmaster

Reputation: 431599

A value has a lifetime of its own, but a reference also tracks the lifetime of the thing it references. Unfortunately, there's a lack of official terminology to use here. The term that I (and some others) have started using is concrete lifetime. There are three variables in main and thus there are three concrete lifetimes:

fn main() {
    let c;                     //       'c
    let mut a = String::new(); // 'a     ¦
    {                          //  |     ¦
        let b = &mut a;        //  | 'b  ¦
        c = foo(b);            //  |  |  |
    }                          //  |     |
    println!("{}", c)          //  |     |
}

a is a String, b is a &mut String, and c is a &str. All three variables are values, but b and c are also references. In this case, b refers to the value in a and is &'a mut String. Since c is derived from b, it has the same "inner lifetime": &'a str.

Notably, the lifetime of b itself never comes into play. It's exceedingly rare for it to, as you need to have mutable borrows and an "extra" borrow:

fn main() {
    let c;
    let mut a = String::new();
    {
        let mut b = &mut a;
        c = foo(&mut b);    // Mutably borrowing `b` here
    }
    println!("{}", c)
}
error[E0597]: `b` does not live long enough
 --> src/main.rs:6:17
  |
6 |         c = foo(&mut b);
  |                 ^^^^^^ borrowed value does not live long enough
7 |     }
  |     - `b` dropped here while still borrowed
8 |     println!("{}", c)
  |                    - borrow later used here

In this case, the value passed to foo is of type &'b mut &'a mut String, which is coerced down to &'b mut str. The value b does not live long enough, and you get the error.

I don't think this model can account for more complicated borrowing relationships. If a is used again after the println!, for example, the mutable borrow can't be for the entire lifetime of a

The mutable borrow of a is held by c, but the duration of the borrow doesn't need to correspond to the lifetime of c. Due to non-lexical lifetimes (better called "non-lexical borrows" in this context), the borrow of a held by c can terminate after the println! but before the end of scope.

Enhancing the the diagram from above to show the lifetime of the value combined with the lifetime of the referred-to value in parenthesis:

fn main() {
    let c;                     //           'c
    let mut a = String::new(); // 'a         ¦
    {                          //  |         ¦
        let b = &mut a;        //  | 'b('a)  ¦
        c = foo(b);            //  |  |('a)  |('a)
    }                          //  |         |('a)
    println!("{}", c);         //  |         |('a)
                               //  |         |
    println!("{}", a);         //  |         |
}

See also:

Upvotes: 2

Related Questions