Search code examples
rusttrait-objects

Trait objects from references


The below is (roughly) the trait object example taken from the rust book chapter 17.2. For my use case, I want to continue to use button and select_box after creating screen (see the commented out println!() after declaring screen), however I am not able since button and select_box are moved into screen. To me it seems the solution is to have screen borrow, instead of take ownership, of select_box and screen. However, I can't work out how to do this. I have tried creating boxes from references like:

let screen = Screen {
    components: vec![Box::new(&select_box), Box::new(&button)],
};

but this generates errors like:

the trait `Draw` is not implemented for `&SelectBox`
fn main() {
    let select_box = SelectBox {
        width: 75,
        height: 10,
        options: vec![
            String::from("Yes"),
            String::from("Maybe"),
            String::from("No"),
        ],
    };
    let button = Button {
        width: 50,
        height: 10,
        label: String::from("OK"),
    };
    let screen = Screen {
        components: vec![Box::new(select_box), Box::new(button)],
    };
    // println!("button width: {}", button.width);
    screen.run();
}

trait Draw {
    fn draw(&self);
}

struct Screen {
    components: Vec<Box<dyn Draw>>,
}

impl Screen {
    fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

struct Button {
    width: u32,
    height: u32,
    label: String,
}

impl Draw for Button {
    fn draw(&self) {
        println!("Button({}, {}, {})", self.width, self.height, self.label)
    }
}

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!(
            "SelectBox({}, {}, {})",
            self.width,
            self.height,
            self.options.join(";")
        )
    }
}

Solution

  • The usual solution is to have a blanket implementation for &T and &mut T where T: Draw:

    impl<T: ?Sized + Draw> Draw for &'_ T {
        fn draw(&self) {
            <T as Draw>::draw(&**self)
        }
    }
    impl<T: ?Sized + Draw> Draw for &'_ mut T {
        fn draw(&self) {
            <T as Draw>::draw(&**self)
        }
    }
    

    However, then you get another error:

    error[E0597]: `select_box` does not live long enough
      --> src/main.rs:17:35
       |
    17 |         components: vec![Box::new(&select_box), Box::new(&button)],
       |                          ---------^^^^^^^^^^^-
       |                          |        |
       |                          |        borrowed value does not live long enough
       |                          cast requires that `select_box` is borrowed for `'static`
    ...
    21 | }
       | - `select_box` dropped here while still borrowed
    
    error[E0597]: `button` does not live long enough
      --> src/main.rs:17:58
       |
    17 |         components: vec![Box::new(&select_box), Box::new(&button)],
       |                                                 ---------^^^^^^^-
       |                                                 |        |
       |                                                 |        borrowed value does not live long enough
       |                                                 cast requires that `button` is borrowed for `'static`
    ...
    21 | }
       | - `button` dropped here while still borrowed
    

    This is because dyn Trait is actually dyn Trait + 'static. You need to add a lifetime parameter:

    struct Screen<'a> {
        components: Vec<Box<dyn Draw + 'a>>,
    }
    
    impl Screen<'_> {
    

    Playground.