Max888
Max888

Reputation: 3750

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(";")
        )
    }
}

Upvotes: 3

Views: 849

Answers (3)

EvilTak
EvilTak

Reputation: 7579

Trait objects can be used via any pointer type (like references, Rc, Arc, etc.), not just Box. So, if you want Screen to borrow components, you can simply store references to the Draw trait objects representing the components:

struct Screen<'c> {
    components: Vec<&'c dyn Draw>,
}

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

Playground

Upvotes: 1

Joe_Jingyu
Joe_Jingyu

Reputation: 1269

You can use Rc instead of Box to make it possible for screen and the main function to reference the two components select_box and button at the same time.

use std::rc::Rc;
fn main() {
    let select_box = Rc::new(SelectBox {
        width: 75,
        height: 10,
        options: vec![
            String::from("Yes"),
            String::from("Maybe"),
            String::from("No"),
        ],
    });
    let button = Rc::new(Button {
        width: 50,
        height: 10,
        label: String::from("OK"),
    });
    let screen = Screen {
        components: vec![select_box.clone(), button.clone()],
    };
    println!("button width: {}", button.width);
    screen.run();
}

trait Draw {
    fn draw(&self);
}

struct Screen {
    components: Vec<Rc<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(";")
        )
    }
}

Playground

Here is the output:

button width: 50
SelectBox(75, 10, Yes;Maybe;No)
Button(50, 10, OK)

Upvotes: 1

Chayim Friedman
Chayim Friedman

Reputation: 70840

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.

Upvotes: 2

Related Questions