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.
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 namedrun
that will call thedraw
method on each of itscomponents
, 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 typeButton
or all of typeTextField
. 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 aVec<T>
that contains aBox<Button>
as well as aBox<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.