3

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Helin Wang
  • 4,002
  • 1
  • 30
  • 34
  • It seems you just answered your own question. What else are you expecting? – SOFe Aug 12 '19 at 06:00
  • Just want to confirm if my logic and guess is correct, and are there any exceptions to my guess? – Helin Wang Aug 12 '19 at 06:01
  • 1
    `b` has the type `&'a mut str`. The lifetime of the reference would be the scope that owns the value. In this case, you didn't move `a` into `'b`, so your reference is still `&'a`. However, if you pass `&b` into `foo` (even if the type matches), you will get error because `b` lives inside `'b`. – SOFe Aug 12 '19 at 06:06
  • 1
    The lifetime of a borrow expression isn't necessarily the same as the lifetime of any variable. The compiler doesn't have to *choose* an output lifetime from the input lifetimes, it can create a "new" lifetime that satisfies the requirements any place there is a borrow expression. (also note `foo(b)` does an implicit reborrow, so the type of `c` and the type of `b` don't need to have the same lifetime parameter) – trent Aug 12 '19 at 16:04

1 Answers1

2

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 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`. – trent Aug 13 '19 at 15:27
  • Better. We're dealing with a lack of precision in terminology, as you mention. It still doesn't match my own mental model, but I might be overfitting. – trent Aug 13 '19 at 16:35