RustArena

Step 1 of 7

dyn Trait: many types in one Vec

Generics are great when a function works with one type at a time. But a drawing app needs one list holding circles, rectangles and triangles together, and a Vec<T> has a single element type:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

struct Square {
    side: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        3.0 * self.radius * self.radius
    }
}

impl Shape for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn main() {
    let shapes = vec![Circle { radius: 1.0 }, Square { side: 2.0 }];
    println!("{}", shapes.len());
}
error[E0308]: mismatched types

The answer is a trait object: dyn Shape means "some value of a type that implements Shape; which type exactly is only known at run time". Because those types have different sizes, a trait object always sits behind a pointer, most often a Box:

use std::f64::consts::PI;

trait Shape {
    fn name(&self) -> String;
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

struct Rect {
    w: f64,
    h: f64,
}

impl Shape for Circle {
    fn name(&self) -> String {
        String::from("circle")
    }
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }
}

impl Shape for Rect {
    fn name(&self) -> String {
        String::from("rect")
    }
    fn area(&self) -> f64 {
        self.w * self.h
    }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 1.0 }),
        Box::new(Rect { w: 2.0, h: 3.0 }),
        Box::new(Circle { radius: 2.0 }),
    ];
    let mut total = 0.0;
    for s in &shapes {
        println!("{}: {:.2}", s.name(), s.area());
        total += s.area();
    }
    println!("total: {:.2}", total);
}
circle: 3.14
rect: 6.00
circle: 12.57
total: 21.71

Piece by piece:

Through a dyn Shape you can call the trait's methods, and nothing else: the compiler no longer knows the concrete type.

Common mistakes

Your task

Implement Shape for Square (name square, area side * side), and write fn total_area(shapes: &[Box<dyn Shape>]) -> f64 that adds up the areas of a mixed list.

Challenge 2

Write fn describe_all(shapes: &[Box<dyn Shape>]) -> Vec<String> that returns one line per shape, <name>: <area with 2 decimals>, like rect: 6.00.

Challenge 3

An alerting system can send a message through several channels. Write a trait Notifier with one method fn send(&self, msg: &str) -> String that returns what would be sent, and implement it:

Then write fn notify_all(notifiers: &[Box<dyn Notifier>], msg: &str) -> Vec<String>.

Next: &dyn Trait and Box<dyn Trait>