Borrowed value does not live long enough when it should be the last value dropped

I've been learning rust and love it so far, but ran into a error message that has me stumped.

trait T1 {}
struct A {}
impl T1 for A {}

fn other(_: &[&mut dyn T1]) {}

fn main() {
    let mut b: Vec<Box<dyn T1>> = vec![];
    let b_ref: Vec<&mut dyn T1> = b.iter_mut().map(|x| &mut **x).collect();
    other(&b_ref);
}

This gives the following error

error[E0597]: `b` does not live long enough
  --> src/main.rs:9:35
   |
9  |     let b_ref: Vec<&mut dyn T1> = b.iter_mut().map(|x| &mut **x).collect();
   |                                   ^^^^^^^^^^^^ borrowed value does not live long enough
10 |     other(&b_ref);
11 | }
   | -
   | |
   | `b` dropped here while still borrowed
   | borrow might be used here, when `b` is dropped and runs the `Drop` code for type `Vec`

My understanding is that b_ref borrows from b, but should also be dropped before b and so shouldn't be a problem. Changing the code in various ways makes the problem disappear for reasons I don't understand either. For instance, it compiles fine if changed so that other takes a slice of immutable references. Similarly, using drop or dbg! instead of other work fine, and using Any instead of T1.

1 Like

This hits on a dyn Trait's "lifetime of a applicability" [1], which you may not be aware of. Every dyn Trait has an associated lifetime, so the type is really dyn Trait + 'lifetime. It has a lot of special behavior (like custom elision rules), in part so that you usually "don't have to think about it". But all the special casing makes it quite complex, and I think you're just hitting on an unfortunate combination of special cases.

It's nothing I'd expect a new comer to know their way around. I believe the TL;DR fix is:

-fn other(_: &[&mut dyn T1]) {}
+fn other<'app>(_: &[&mut (dyn T1 + 'app)]) {}

Though I'd be curious to hear how you ran into this, if you were trying something practical.


Now I'll walk through what I believe is going on.

fn other(_: &[&mut dyn T1]) {}

Here, the dyn T1 is behind a reference, so the default lifetime of applicability is taken to be the same as that of the reference. The function ends up behaving like so:

fn other<'out, 'inner>(_: &'out [&'inner mut (dyn T1 + 'inner)]) {}

Then in main:

    let mut b: Vec<Box<dyn T1>> = vec![];

Since this is in a function body, the lifetime of applicability is inferred [2]. The inference is influenced by the call to other -- if you don't call other (e.g. you just call Drop), the inference may be different (and hence may compile).

But with other, the end result is that you end up with the contents of the Vec being borrowed for the rest of their lifetime. This happens whenever you have a &'a mut SomeThing<'a> and is an anti-pattern -- you can never use a struct borrowed like this again. In this case, the Drop implementation is trying to "use" the structs within the Vec again, and you get an error.

There's a lot of potential subtleties here [3] that I won't pursue.


So why does Any work?

Any has a 'static bound. If you add such a bound to T1:

trait T1: 'static {}

Then the default lifetime of applicability becomes 'static, and this:

fn other(_: &[&mut (dyn T1)]) {}

Acts like this:

fn other<'out, 'inner>(_: &'out [&'inner mut (dyn T1 + 'static)]) {}

Playground.

This hints at another way to get things to compile: Leave the bound off the trait, but explicitly require a 'static lifetime of applicability on other:

fn other(_: &[&mut (dyn T1 + 'static)]) {}

Can we avoid requiring 'static? A first attempt is to keep the signature of other and adjust the Vec (moving where we require 'static to see if it's viable for other):

fn other(_: &[&mut dyn T1]) {}
// &'out[&'inner mut (dyn T1 + 'inner)]

fn main() {
    let mut b: Vec<Box<dyn T1 + 'static>> = vec![];

But because the lifetime of applicability is implied to be the same as the reference, we get:

error[E0597]: `b` does not live long enough
  --> src/main.rs:9:35
   |
8  |     let mut b: Vec<Box<dyn T1 + 'static>> = vec![];
   |                -------------------------- type annotation requires that `b` is borrowed for `'static`

However, we can break the equality by giving the lifetime of applicability an explicitly fresh lifetime parameter:

fn other<'app>(_: &[&mut (dyn T1 + 'app)]) {}

And in fact, once the equality is broken, the lifetime in the Box need no longer be 'static either, and we can let it be inferred again.

This version allows non-'static implementers of the trait.

impl<'a> T1 for &'a A {}

fn other<'app>(_: &[&mut (dyn T1 + 'app)]) {}

fn main() {
    let a = A {};
    let mut b: Vec<Box<dyn T1>> = vec![Box::new(&a)];

And that's the diff I suggested at the top.


Again, all pretty obscure stuff that I wouldn't expect someone to know when learning Rust.


  1. not an official term ↩︎

  2. even though elsewhere, being in a Box implies 'static ↩︎

  3. role of #[may_dangle]? ↩︎

6 Likes

It also works with an anonymous lifetime:

fn other(_: &[&mut (dyn T1 + '_)]) {}
5 Likes

Wow, I've been using rust pretty extensively for a few years now and this is pretty much all new to me! I guess you're never not learning Rust!

Thanks, that does help clear things up. I hadn't realized that 'the rest of their lifetime' extended through the point where the Vec was dropped.

What I originally was trying to do was take a slice of type with the trait, while maintaining full type information in the calling function. 'b' only contained boxes in some cases. There probably a better way to do what I was trying to do, and there was certainly other workarounds I thought of, but this was the first solution I thought of.

And now I know why it didn't work. Thanks again!