Why I get "borrowed value does not live long enough"?

Here is a sample code to verify the behavior of lifetime. I got a compile error.

struct Child<'a> {
    name: &'a str,
}

struct Parent<'a> {
    child: &'a Child<'a>,
}

impl<'a> Parent<'a> {
    fn new(chd: &'a mut Child<'a>) -> Self {
        Parent {
            child: chd,
        }
    }
}

fn test(mut chd: Child) {
    let _parent = Parent::new(&mut chd);
}

fn main () {
    let child: Child = Child{ name: "Jhon" };
    test(child);
}
error[E0597]: `chd` does not live long enough
  --> test.rs:18:31
   |
17 | fn test(mut chd: Child) {
   |         ------- has type `Child<'1>`
18 |     let _parent = Parent::new(&mut chd);
   |                   ------------^^^^^^^^-
   |                   |           |
   |                   |           borrowed value does not live long enough
   |                   argument requires that `chd` is borrowed for `'1`
19 | }
   | - `chd` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

I have some resolutions to fix the error, But I don't know why I can fix it.

  1. Create local value tmp and pass chd.
fn test(mut chd: Child) {
    let mut tmp = chd;
    let _parent = Parent::new(&mut tmp);
}
  1. Set lifetime to each reference and struct.
struct Parent<'a, 'b> {
    child: &'a Child<'b>,
}

impl<'a,'b> Parent<'a,'b> {
    fn new(chd: &'a mut Child<'b>) -> Self {
        Parent {
            child: chd,
        }
    }
}
  1. Change chd immutable
impl<'a> Parent<'a> {
    fn new(chd: &'a Child<'a>) -> Self {
        Parent {
            child: chd,
        }
    }
}

fn test(chd: Child) {
    let _parent = Parent::new(&chd);
}

IMHO, after Parent::new() executed, the Child should be mutable borrowed while the Child is alive.
And then, the Child drops at the end of test() and Child mutable borrowing also ends. Thus, Child lived long enough, I think.

So my question is,

  • Why does this error happen in spite of Child lives long enough?
  • (if you can) why can I workaround the error by methods 1-3?

Could I hear your opinions, please?

Thanks,

First let's elide less lifetimes and rename others so it's easier to talk about.

Next, your immediate error involving:

fn test<'cn>(mut chd: Child<'cn>) {
    let _parent = Parent::new(&mut chd);
}

When you have generic parameters on a function, the caller chooses the concrete value [1] of the parameter, within any bounds. For unbound lifetimes, the only thing you know for sure is that the lifetime lasts just longer than your function all at a minimum (so the lifetime is valid for the entire body of the function).

So in this case, the lifetime could be any lifetime longer than the function body at all, like 'static for instance.

The next piece of the puzzle is that when you have a lifetime behind a &mut:

    fn new(chd: &'pc mut Child<'pc>) -> Self {

The inner lifetime is invariant -- it cannot be coerced to be shorter or longer; it cannot change at all. This pattern in particular: &'a mut Something<'a> -- is an antipattern, because it requires that the Something be borrowed for the entirety of its lifetime, rendering it inaccessible through anything but the outer &mut, even after the &mut has itself been discarded. Forcing the lifetimes to be the same is very problematic in this pattern.

That's why you're getting the error about the borrow being longer than the function -- you don't control the inner lifetime, the caller does, and your signature is forcing the outer lifetime to be as long as the inner lifetime. This won't work if the lifetime is 'static for presumably obvious reasons -- but again, the only thing you do know about the lifetime is that it's longer than the body of your function, so in fact, no lifetime at all can work with this pattern so long as the caller is choosing the lifetimes.

Next, the workarounds. Every one involves either

  • Not using the caller-provided lifetime for the inner lifetime, or
  • Breaking the equality between the outer and inner lifetimes

As doing either one avoids forcing the outer lifetime to be as long as the caller-chosen lifetime (longer than the function body).

  1. Create local value tmp and pass chd.

The Child struct itself is covariant , as the lifetime just corresponds to a reference. When you assign to tmp, it gets coerced to some shorter lifetime local to the function body. The original caller-chosen lifetime (cn) is discarded.

  1. Set lifetime to each reference and struct.

This avoids forcing the inner and outer lifetimes to be equal and is probably what you want, for the &mut case at least. (Often you can get away with a single lifetime for &'a Something<'a> because the inner value is still covariant with a shared reference, in contrast with a &mut. However that doesn't work without further changing the signatures you've provided.)

With this approach, you create a &mut with a lifetime that doesn't escape the function body. This is possible as it's not forced to be the same as the inner, caller-provided lifetime (cn).

  1. Change chd immutable

This is the "getting away with a single lifetime for &'a Something<'a>" situation mentioned above. You create a borrow local to the function, and the inner lifetime is free to coerce to that shorter lifetime (something shorter than the caller-provided cn). It's sort of similar to the first case but can happen after both lifetime are combined, unlike with &mut on the outside.


You can read more about variance in the reference.


  1. lifetime, type... ↩ī¸Ž

8 Likes

This won't work. References are a wrong tool for this job, and this approach is a dead-end. Rust references aren't merely a way to refer to objects. They are scope-bound temporary loans, and this comes with restrictions that make most structs useless. Rust forbids self-referential types, &mut makes loans exclusive blocking every other use of their target. There's no amount of lifetime syntax that will make it work. In general, you should not put references in structs.

in Rust parent<>child relationships must use something else, like Arc (or Rc) and Weak, or some other way to reference items, e.g. generational indices.

You will have more flexibility if you can live without the parent field. This will let you use Box<Child> to store children (Box is like &mut, but doesn't cause the horrible lifetime infection, because it can live for as long as it likes).

3 Likes