Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing error in Rust with trait object lifetime

Can anyone tell what the problem is with the following code? The compiler is complaining about lifetimes, but the error message makes absolutely no sense. I've tried everything I could think of, but nothing seems to help.

use std::borrow::BorrowMut;

trait Trait<'a> {
    fn accept(&mut self, &'a u8);
}

struct Impl<'a>{
    myref: Option<&'a u8>,
}
impl<'a> Trait<'a> for Impl<'a> {
    fn accept(&mut self, inp: &'a u8) { self.myref = Some(inp); }
}

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl{myref: None})
}

fn user<'a>(obj: &mut Trait<'a>) {}

fn parent<'a>(x: &'a u8) {
    let mut pool = new();
    user(pool.borrow_mut());
}

The compiler error is

error: `pool` does not live long enough
  --> src/wtf.rs:22:10
   |
22 |     user(pool.borrow_mut());
   |          ^^^^ does not live long enough
23 | }
   | - borrowed value dropped before borrower
   |
   = note: values in a scope are dropped in the opposite order they are created

Which makes absolutely no sense. How is the borrower outliving anything? I'm not even using the borrowed value!

like image 571
Antimony Avatar asked Sep 06 '16 01:09

Antimony


3 Answers

Ok, this does make sense, but it's hard to see due to lifetime elision. So, here's your code with all the lifetimes written out explicitly, and with irrelevant details culled:

use std::borrow::BorrowMut;

trait Trait<'a> {}

struct Impl<'a> {
    myref: Option<&'a u8>,
}

impl<'a> Trait<'a> for Impl<'a> {}

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl { myref: None })
}

fn user<'a, 'b>(obj: &'b mut (Trait<'a> + 'b)) {}

fn parent() {
/* 'i: */   let mut pool/*: Box<Trait<'x> + 'x>*/ = new();
/* 'j: */   let pool_ref/*: &'i mut Box<Trait<'x> + 'x>*/ = &mut pool;
            /* BorrowMut<T>::borrow_mut<'d>(&'d mut Self) -> &'d mut T */
/* 'k: */   let pool_borrow/*: &'i mut (Trait<'x> + 'x)*/ = Box::borrow_mut(pool_ref);
            user(pool_borrow);
}

Now, from the perspective of the last line of parent, we can work out the following equivalences by just reading the definition of user and substituting the lifetimes we have in parent:

  • 'a = 'x
  • 'b = 'i
  • 'b = 'x

Furthermore, this lets us conclude that:

  • 'x = 'i

This is the problem. Because of the way you've defined user, you've put yourself in a situation where the lifetime of the pool_ref borrow (which is equal to the lifetime of the pool storage location you're borrowing from) must be the same as the lifetime 'x being used in the thing being stored in pool.

It's a bit like the Box being able to have a pointer to itself before it exists, which doesn't make any sense.

Either way, the fix is simple. Change user to actually have the correct type:

fn user<'a, 'b>(obj: &'b mut (Trait<'a> + 'a)) {}

This matches the type produced by new. Alternately, just don't use borrow_mut:

user(&mut *pool)

This works because it is "re-borrowing". Calling borrow_mut translates the lifetimes more or less directly, but re-borrowing allows the compiler to narrow the borrows to shorter lifetimes. To put it another way, explicitly calling borrow_mut doesn't allow the compiler enough freedom to "fudge" the lifetimes to make them all line up, re-borrowing does.

As a quick aside:

I'm not even using the borrowed value!

Irrelevant. Rust does type- and lifetime-checking entirely locally. It never looks at the body of another function to see what it's doing; it goes on the interface alone. The compiler neither checks, nor cares, what you're doing inside a different function.

like image 192
DK. Avatar answered Nov 14 '22 08:11

DK.


Note that there's more to the error message:

error: `pool` does not live long enough
  --> src/main.rs:25:10
   |>
25 |>     user(pool.borrow_mut());
   |>          ^^^^
note: reference must be valid for the block at 23:25...
  --> src/main.rs:23:26
   |>
23 |> fn parent<'a>(x: &'a u8) {
   |>                          ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 24:25
  --> src/main.rs:24:26
   |>
24 |>     let mut pool = new();
   |>                          ^

Let's look at user:

fn user<'a>(obj: &mut Trait<'a>) {}

This says that it will accept a mutable reference (with an unnamed lifetime) to a trait object parameterized with the lifetime 'a.

Turning to new, I'd say the method is highly suspicious:

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl { myref: None })
}

This says that it will return a boxed trait object with whatever lifetime the caller specifies. That basically never makes sense.

All that said, I'm not clear why the code chooses to use borrow_mut. I would have written that more directly:

user(&mut *pool);

This dereferences the Box<Trait> to get a Trait, then takes a mutable reference, yielding &mut Trait, which compiles.

I cannot currently explain why BorrowMut differs in behavior.

like image 39
Shepmaster Avatar answered Nov 14 '22 06:11

Shepmaster


I'm not sure why this error happens, but I can give solutions!

First, it seems that using borrow_mut unnecessarily restricts the lifetime of the returned reference. Using operators to create the reference solves the error.

fn parent() {
    let mut pool = new();
    user(&mut *pool);
}

However, if we don't do that, we can solve the error by adding a lifetime bound to the Trait object in user's obj argument.

fn user<'a>(obj: &mut (Trait<'a> + 'a)) {}
like image 2
Francis Gagné Avatar answered Nov 14 '22 07:11

Francis Gagné