Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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(";")
        )
    }
}
like image 284
Max888 Avatar asked Sep 22 '26 14:09

Max888


1 Answers

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.

like image 135
Chayim Friedman Avatar answered Sep 25 '26 02:09

Chayim Friedman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!