Strange lifetime error when using borrow_mut

f81db70
Opened by Jay at 2022-03-21 17:46:34
use std::borrow::BorrowMut;

trait A {}

struct B;

impl A for B {}

fn func(a: &mut A) {}

fn main() {
    let t: Box<A> = Box::new(B);
    {
        let b = t.borrow_mut();
        func(b);
    }
}

When compiling above code, it fails with:

error: `t` does not live long enough
  --> <anon>:17:1
   |
14 |         let b = t.borrow_mut();
   |                 - borrow occurs here
...
17 | }
   | ^ `t` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

I think the borrow state should end at L16.

  1. A is a trait so &mut A is short for &'a mut (A + 'a), which means that... wait, why do we have that, it means that it can borrow itself.

    cc @nikomatsakis Is this useful behavior at all? Do we do something with reborrows?

    Eduard-Mihai Burtescu at 2017-06-06 14:25:20

  2. So, a couple of things:

    First, to make this example work, you only need to change from t.borrow_mut() to &mut *t (you also need to make it let mut t). Using borrow_mut() in this way is definitely not recommended, but you could also make it work by adding a let b: &mut A annotation.

    Now, as to why we get the error. As @eddyb points out, this is fallout from the defaulting rules around trait objects. In particular, &mut A defaults to &'a mut (A + 'a). There are reasons for this that I'll get to in a bit, but it has some drawbacks too. In any case, Box<A> defaults to Box<A + 'static>. If you were using &mut *t instead of t.borrow_mut(), then the type of b would be known to be &mut (A + 'static), and we have a built-in coercion that converts from &'a mut (A + 'static) to &'a mut (A + 'a).

    When you use t.borrow_mut(), without the type annotation, the resulting type is a type inference variable, and so the coercion doesn't wind up kicking in. When you use the type annotation, the coercion kicks in.

    The reason that the trait object lifetime rules are the way they are is that they apply everywhere, not just in function signatures, and in those other cases we can't supply a fresh lifetime -- it's important that the lifetimes apply everywhere, because otherwise we have the same type (i.e., same characters) in two places with incompatible meanings. In general, then, we say that if the trait object appears inside a reference, we will use the lifetime of that reference (even if the reference is &mut). This means that &Trait or &mut Trait allows the type implementing the trait to have references in it.

    (Well, in function bodies, I Think we always use inference variables, that's the last part of the puzzle.)

    In any case, i'm closing this as expected behavior -- I'm not sure that there's anything else we can do here.

    Niko Matsakis at 2017-06-07 21:52:59

  3. (Maybe a better error message? But this case (using borrow_mut() this way) doesn't feel very representative, so I'm not just I'd want to do it for this particular example -- maybe we can find another one that fails in a similar way?)

    Niko Matsakis at 2017-06-07 21:53:47

  4. My theory is that the lifetime variable in Box<A> has to outlive the binding it's a type of, which later extends the lifetime variable &'a mut (A + 'a) to be longer.

    Eduard-Mihai Burtescu at 2017-06-07 22:00:24

  5. Mm so I take it back. It occurs to me that we could plausibly override the object lifetime default in fn arg position, just as we override it in the body of a fn, to get a fresh lifetime variable -- specifically in the case where you have &Trait or &mut Trait. This would require some lang-team discussion, probably an (amendment) RFC would be best. But if it's truly backwards compatible it seems like a win-win.

    Niko Matsakis at 2017-06-07 22:31:07

  6. My theory is that the lifetime variable in Box<A> has to outlive the binding it's a type of, which later extends the lifetime variable &'a mut (A + 'a) to be longer.

    Yes, you're right of course -- but the rest of my comment remains accurate, just not the part about Box<A> being Box<A+'static>.

    Niko Matsakis at 2017-06-08 00:01:00

  7. Triage: no change

    Maayan Hanin at 2022-03-21 17:46:34