rusttraitsbox

The source code on Rust Book Chapter 17 may be wrong


Initially I use this tutorial as reference for some practice code. After a few hours of troubleshooting I just plainly tried the code on that page as is.

https://doc.rust-lang.org/book/ch17-02-trait-objects.html

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

pub trait Draw {
    fn draw(&self);
}

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

impl Draw for Button {
    fn draw(&self) {
        // code to actually draw a button
    }
}
pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T>
where
    T: Draw,
{
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

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

impl Draw for SelectBox {
    fn draw(&self) {
        // code to actually draw a select box
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    fn test_tell_me(){
        let screen = Screen {
            components: vec![
                Box::new(SelectBox {
                    width: 75,
                    height: 10,
                    options: vec![
                        String::from("Yes"),
                        String::from("Maybe"),
                        String::from("No"),
                    ],
                }),
                Box::new(Button {
                    width: 50,
                    height: 10,
                    label: String::from("OK"),
                }),
            ],
        };
    
        screen.run();
    }
}

Errors:

warning: `decision-command-parser` (lib) generated 1 warning

error[E0277]: the trait bound `Box<SelectBox>: Draw` is not satisfied
  --> src/lib.rs:60:25
   |
60 |               components: vec![
   |  _________________________^
61 | |                 Box::new(SelectBox {
62 | |                     width: 75,
63 | |                     height: 10,
...  |
74 | |                 }),
75 | |             ],
   | |_____________^ the trait `Draw` is not implemented for `Box<SelectBox>`
   |
   = help: the following other types implement trait `Draw`:
             Button
             SelectBox

bard.google.com says I need to add the trait to the Button, but the tutorial should be the source of truth given that it is "The Book" and all.


Solution

  • The book is quite clear, I think:

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

    On the Screen struct, we’ll define a method named run that will call the draw method on each of its components, as shown in Listing 17-5:

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

    This works differently from defining a struct that uses a generic type parameter with trait bounds. A generic type parameter can only be substituted with one concrete type at a time, whereas trait objects allow for multiple concrete types to fill in for the trait object at runtime. For example, we could have defined the Screen struct using a generic type and a trait bound as in Listing 17-6:

    pub struct Screen<T: Draw> {
        pub components: Vec<T>,
    }
    
    impl<T> Screen<T>
    where
        T: Draw,
    {
        pub fn run(&self) {
            for component in self.components.iter() {
                component.draw();
            }
        }
    }
    

    This restricts us to a Screen instance that has a list of components all of type Button or all of type TextField. If you’ll only ever have homogeneous collections, using generics and trait bounds is preferable because the definitions will be monomorphized at compile time to use the concrete types.

    On the other hand, with the method using trait objects, one Screen instance can hold a Vec<T> that contains a Box<Button> as well as a Box<TextField>. Let’s look at how this works, and then we’ll talk about the runtime performance implications.

    So, the generic approach can be used for a homogeneous collection, and the trait objects approach can be used for heterogeneous. You tried to use the generic approach with multiple types, which it cannot do, as the books says.