Search code examples
rustlifetimeborrow-checkertrait-objects

Drop order for when boxed struct contains reference


I'm trying to make a node structure but I have no idea why this won't compile (Rust playground):

trait SomeTrait {}

struct SomeObject<'a> {
    something: &'a dyn SomeTrait,
}
impl<'a> SomeTrait for SomeObject<'a> {}

struct OtherObject {}
impl SomeTrait for OtherObject {}

pub struct Node {
    children: Vec<Box<dyn SomeTrait>>,
}
fn main() {
    let a = vec![OtherObject {}];

    let b: Vec<Box<dyn SomeTrait>> = a
        .iter()
        .map(|d| Box::new(SomeObject { something: d }) as Box<dyn SomeTrait>)
        .collect();

    //But if i comment this it's fine... why?
    Box::new(Node { children: b });
}
error[E0597]: `a` does not live long enough
  --> src/main.rs:17:38
   |
17 |     let b: Vec<Box<dyn SomeTrait>> = a
   |                                      ^ borrowed value does not live long enough
18 |         .iter()
19 |         .map(|d| Box::new(SomeObject { something: d }) as Box<dyn SomeTrait>)
   |                  ----------------------------------------------------------- returning this value requires that `a` is borrowed for `'static`
...
24 | }
   | - `a` dropped here while still borrowed

Why does it say that a is still in use? Shouldn't the other variables be dropped before?


Solution

  • Fixed version:

    trait SomeTrait {}
    
    struct SomeObject<'a> {
        something: &'a dyn SomeTrait,
    }
    impl<'a> SomeTrait for SomeObject<'a> {}
    
    struct OtherObject {}
    impl SomeTrait for OtherObject {}
    
    pub struct Node<'a> {
        children: Vec<Box<dyn SomeTrait + 'a>>,
    }
    
    fn main() {
        let a = vec![OtherObject {}];
    
        let b: Vec<Box<dyn SomeTrait>> = a
            .iter()
            .map(|d| Box::new(SomeObject { something: d }) as Box<dyn SomeTrait>)
            .collect();
    
        Box::new(Node { children: b });
    }
    

    So, what's the problem ?

    pub struct Node {
        children: Vec<Box<dyn SomeTrait>>,
    }
    

    is the same as

    pub struct Node {
        children: Vec<Box<dyn SomeTrait + 'static>>,
    }
    

    which means (1,2) that the SomeTriat objects must not hold any references that are not 'static. But you have:

    .map(|d| Box::new(SomeObject { something: d }) as Box<dyn SomeTrait>)
    

    where |d| is actually a reference that does not live for as long as 'static (it is valid for as long as the vector a is in scope, which is less than 'static), therefore the error message:

    returning this value requires that `a` is borrowed for `'static`
    

    By making your Node object generic over the lifetime parameter 'a, you can lift that restriction. After that change, your Node<'a> objects will be bounded by the lifetime of .map(|d|...) reference

    Resources: