Lifetime issue when calling method on a boxed trait

Sorry about my stupid question. I got a compliation error for below code:

trait Data<'a> {
    fn dump(&'a mut self);    // this needs to be mutable by intention, as the code is highly reduced
}

struct MyData<'a> {
    val1: &'a mut u8,
    val2: u8
}

impl<'a> Data<'a> for MyData<'a> {
    fn dump(&'a mut self)
    {
        println!("val = {}", self.val2);
    }
}

fn create_data<'a>(num: &'a mut u8) -> Box<dyn Data<'a> + 'a>
{
    Box::new(MyData { val1: num, val2: 8 })
}

fn main()
{
    let mut a: u8 = 4;
    let mut x = create_data(&mut a);
    x.dump();
    // Box::leak(x).dump(); // this can pass
}

I'm a bit confused by below error message. Shouldn't x and its inner value has the same lifetime as a? If not, how can I correct it? Thanks in advance for any help or insight.

error[E0597]: `*x` does not live long enough
  --> src\main.rs:28:5
   |
28 |     x.dump();
   |     ^ borrowed value does not live long enough
29 | }
   | -
   | |
   | `*x` dropped here while still borrowed
   | borrow might be used here, when `x` is dropped and runs the destructor for type `Box<dyn Data<'_>>`

error: aborting due to previous error

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

By explicitly inserting lifetimes, you are claiming to borrow self for longer than you wanted to. Remove some lifetimes, or change them such that dump doesn't borrow x for too long.

Since any references in a struct must have a lifetime that lasts until after the struct is destroyed, the lifetime 'a must last until after x is destroyed.

However, you also reuse the same lifetime 'a on the call to dump, so dump must borrow x for at least the lifetime 'a. Thus, since 'a lasts until after the destructor of x runs, the call to dump must borrow x until after the destructor of x runs.

However it is illegal for a struct to still be borrowed when a destructor runs, so you get an error.

2 Likes

To fix it, remove lifetimes that tell rust to borrow things for longer than they need to be borrowed.

trait Data {
    fn dump(&mut self); // this needs to be mutable by intention, as the code is highly reduced
}

struct MyData<'a> {
    val1: &'a mut u8,
    val2: u8,
}

impl<'a> Data for MyData<'a> {
    fn dump(&mut self) {
        println!("val = {}", self.val2);
    }
}

fn create_data<'a>(num: &'a mut u8) -> Box<dyn Data + 'a> {
    Box::new(MyData { val1: num, val2: 8 })
}

fn main() {
    let mut a: u8 = 4;
    let mut x = create_data(&mut a);
    x.dump();
}

Though I find it likely that you don't need the mutable reference in the struct in the first place. Most people don't need references in structs.

2 Likes

Appreciate it! That explains.

Hi @alice

Just want to understand further about trait objects with lifetime. Below is another reduced example. The trait with lifetime bound is now implemented on a non-struct u8 primitive. The lifetime isn't useful in this case but I just want to understand how Rust inteprets it.

trait Data<'a> {
    fn dump(&'a mut self);
}

impl<'a> Data<'a> for u8 {
    fn dump(&'a mut self) {
    }
}

fn main()
{
    let mut x = Box::new(0u8) as Box<dyn Data>;
    // let mut x = Box::new(0u8); // this can pass
    x.dump();
}

When casted into the Data trait, it produced the same error as below. What's the underlying difference in lifetime when calling the same dump on a borrowed trait object and a non-trait primitive?

error[E0597]: `*x` does not live long enough
  --> src\main.rs:15:5
   |
15 |     x.dump();
   |     ^ borrowed value does not live long enough
16 | }
   | -
   | |
   | `*x` dropped here while still borrowed
   | borrow might be used here, when `x` is dropped and runs the destructor for type `Box<dyn Data<'_>>`

error: aborting due to previous error

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

Talking about the type Box<dyn Data> is misleading when the trait has a lifetime. You are really talking about the type Box<dyn Data<'a>> for some specific lifetime 'a. However, a Box<dyn Data<'a>> cannot exist after the end of the lifetime 'a. Unfortunately, you then proceed to call dump, which borrows x until at least the end of 'a and we have the same issue as before.

It works with Box<u8> because that type can exist for as long as it wants to. Here, you are calling dump from the Data<'some_short_lifetime> trait, and x is dropped after the borrow ends.

Your Data trait probably shouldn't have a lifetime.

Thanks @alice. Yeah, in my real working code I removed all unnecessary lifetime. Just want to dive more. So appologize for more questions:

Where is the end of lifetime of 'a for Box<dyn Data<'a>>? Is it the end of let statement? If so, it looks too short to be useful for a trait object with lifetime. And do you know any use case of traits with lifetime?

Below code implements the trait on &u8. I expected the lifetime 'a to be as long as w but it still hits the same error. So I'm a bit puzzled on the purpose of traits with lifetime.

impl<'a> Data<'a> for &'a u8 {
    fn dump(&'a mut self) {
    }
}

fn main()
{
    let mut w = 0u8;
    let mut x = Box::new(&w) as Box<dyn Data>;
    x.dump();
}

&'a mut T<'a> (in this case, &'a mut &'a u8) is almost always not what you want:

  • When you have &'a mut T, it means that T must live for at least 'a, or this mutable borrow would dangle.
  • When you have T<'a>, is means that T must live for at most 'a, or some pointer or borrow inside it will dangle.
  • Combined, these two things mean that T lives for exactly 'a, i.e. it must be dropped together with the borrow - not earlier. Or, putting it another way, the borrow will disappear just before the value is dropped - not later.
  • So, the borrow is extended for much longer then it is really accessed.
1 Like
  • When you have T<'a> , is means that T must live for at most 'a , or some pointer or borrow inside it will dangle.

Thumb up. This clears up my misunderstanding for T<'a>.

I was about to ask you about why below code works before I recalled @alice 's comment above. Data {...} has no way to set its explicit lifetime when constructing. But MyData or u8 in earlier examples do, because they were cast to trait Data<'a>.

struct Data<'a> {
    x: &'a u8
}

impl<'a> Data<'a> {
    fn dump(&'a mut self) {
    }
}

fn main()
{
    let mut x = Box::new(Data { x: &0 });
    x.dump();  // no error
}

Again, thank you and @alice's patience to address my questions. But lastly, what is the use case of a trait with lifetime, given types implementing it cannot be borrowed freely. Is that to be used in parameters other than self?

They can, if one doesn't link the trait's lifetime to the methods input lifetime. A somewhat contrived, but illustrative example:

trait Getter<'a> {
   // We require 'b to be shorter then 'a, but don't restrict how short it is
    fn get<'b>(&'b self) -> &'a u8
    where
        'a: 'b;
}

impl<'a> Getter<'a> for &'a u8 {
    fn get<'b>(&'b self) -> &'a u8
    where
        'a: 'b,
    {
        *self
    }
}

fn main() {
    let x = 0u8;
    let x_ref = &x;

    {
        // It works, since `get` releases the borrow immediately
        let _ = x_ref.get();
    }
    {
        // And we can do it again, if we want
        let _ = x_ref.get();
    }
}

Playground

1 Like

Lifetimes on traits are very rare.

2 Likes

They can, if one doesn't link the trait's lifetime to the methods input lifetime.

Thanks. This is the key.