RustArena

Step 2 of 7

&dyn Trait and Box<dyn Trait>

A trait object can sit behind any kind of pointer. The two you'll use most:

Form Meaning Use it for
&dyn Trait borrows some value that implements the trait parameters that only use the value
&mut dyn Trait borrows it mutably parameters that change it (&mut dyn std::fmt::Write)
Box<dyn Trait> owns it, on the heap storing in a Vec or a struct field, returning from a function

You've already met one: Box<dyn Error> from the custom errors module is a boxed trait object.

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

struct Circle {
    radius: f64,
}

struct Square {
    side: f64,
}

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

impl Shape for Square {
    fn name(&self) -> String {
        String::from("square")
    }
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn report(s: &dyn Shape) -> String {
    format!("{} with area {:.1}", s.name(), s.area())
}

fn make_shape(kind: &str, size: f64) -> Box<dyn Shape> {
    if kind == "circle" {
        Box::new(Circle { radius: size })
    } else {
        Box::new(Square { side: size })
    }
}

fn main() {
    let c = Circle { radius: 1.0 };
    println!("{}", report(&c));

    let shapes = vec![make_shape("square", 3.0), make_shape("circle", 2.0)];
    for s in &shapes {
        println!("{}", report(s.as_ref()));
    }
}
circle with area 3.1
square with area 9.0
circle with area 12.6

From a Box to a &dyn

To pass a boxed shape to report, borrow the shape inside the box: s.as_ref() or &**s (one * for the &, one for the Box). Writing report(s) or report(&boxed) doesn't work, and the error is confusing:

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

struct Square {
    side: f64,
}

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

fn report(s: &dyn Shape) -> f64 {
    s.area()
}

fn main() {
    let b: Box<dyn Shape> = Box::new(Square { side: 2.0 });
    println!("{}", report(&b));
}
error[E0277]: the trait bound `Box<dyn Shape>: Shape` is not satisfied

Rust tries to treat the Box itself as the shape, and a Box doesn't implement Shape. report(b.as_ref()) hands over the square inside.

Common mistakes

Your task

Write fn parse_shape(line: &str) -> Option<Box<dyn Shape>> that turns a line of a drawing file into a shape: circle <radius>, rect <w> <h> or square <side> (use the Rect type for squares too). Return None for anything else, including bad numbers.

Challenge 2

Write fn biggest(shapes: &[Box<dyn Shape>]) -> Option<&dyn Shape> that returns a reference to the shape with the largest area (the first one on a tie), or None for an empty list. Return the shape inside the box with .as_ref().

Challenge 3

std::fmt::Write is the standard trait for "something you can write text into": a String implements it, and so can your own types. Write fn render_receipt(out: &mut dyn std::fmt::Write, items: &[(&str, u32)]) -> std::fmt::Result that writes one line per item, <name>: <dollars>.<cents>, then total: <dollars>.<cents>, each followed by a newline. Prices are in cents. Use writeln!(out, ...)?.

Previous: dyn Trait: many types in one Vec Next: Static vs dynamic dispatch