Skip to content
Learn Rust
Theme

Trait Objects (dyn)

Abstractions · Lesson 35

A trait object is a value whose exact type is forgotten, and only the trait's methods remain. It is how one list holds squares and rectangles, or one variable holds "whichever shape we picked at run time". You write dyn Shape, and you hold it behind a pointer.

Coming from Go&dyn Shape and Box<dyn Shape> are interface values: a pointer to the data plus a pointer to a table of methods. Vec<Box<dyn Shape>> is your []Shape. The differences: a type must say impl Shape for Square first, a &dyn Shape points at the original where a Go interface copies the value, and in Rust you choose per use between this and a generic <T: Shape>, which is decided at compile time.

Coming from KotlinA Box<dyn Shape> is a Shape reference: the object sits on the heap and each call finds the right method at run time. In Kotlin every call through an interface works like that. In Rust it is something you ask for with dyn. A Kotlin <T : Shape> is erased and still an interface call; Rust's <T: Shape> is compiled once per type, with a direct call.

Coming from PythonPython needs nothing like this to hold a mix: any object with an area() method goes in a list, and the method is found by name when the call runs. In Rust the list is Vec<Box<dyn Shape>>, each type must say impl Shape first, and a type without it is refused at compile time, not when the call runs.

Coming from TypeScriptShape[] is Vec<Box<dyn Shape>>. TypeScript checks shape (any object with an area() counts) and the interface is erased at run time. In Rust a type must say impl Shape for Square, and a dyn Shape carries its method table with it.

Coming from JavaList<Shape> is Vec<Box<dyn Shape>>. In Java every object sits behind a reference and an interface call picks the method by the object's class at run time. Rust asks you to write both parts, Box and dyn. A Java <T extends Shape> is erased and still an interface call; Rust's <T: Shape> is compiled once per type, with a direct call.

Coming from C#List<IShape> is Vec<Box<dyn Shape>>. A class instance is always behind a reference in C# and an interface call picks the method by the object's type at run time. Rust asks you to write both parts, Box and dyn. Its generic <T: Shape> is closest to a C# generic with a struct type argument, and a struct behind an interface is boxed, as a Square is in a Box<dyn Shape>.

Coming from Cmain.c builds a dyn by hand: a struct holding a data pointer and a pointer to a table of function pointers. A &dyn Shape is the same idea, filled in and checked by the compiler, with drop, size and alignment added to the table.

Coming from C++Box<dyn Shape> is a std::unique_ptr<Shape> calling virtual methods, with one difference in layout: the pointer to the table sits in the pointer, not inside every object, so Square stays a plain struct and only a dyn pays for the table.

The quick comparison

Rustsrc/main.rsRun ↗
trait Shape {
fn name(&self) -> String;
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn name(&self) -> String {
String::from("square")
}
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn name(&self) -> String {
String::from("rect")
}
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
// a Vec holds one type, so the list holds boxes: owned pointers to heap values
let shapes: Vec<Box<dyn Shape>> =
vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })];
let mut total = 0;
for s in &shapes {
println!("{}: {}", s.name(), s.area());
total += s.area();
}
println!("total: {total}");
}
$ cargo run
square: 9
rect: 24
total: 33
package main
import "fmt"
type Shape interface {
Name() string
Area() int
}
type Square struct{ Side int }
type Rect struct{ W, H int }
func (Square) Name() string { return "square" }
func (s Square) Area() int { return s.Side * s.Side }
func (Rect) Name() string { return "rect" }
func (r Rect) Area() int { return r.W * r.H }
func main() {
shapes := []Shape{Square{3}, Rect{4, 6}}
total := 0
for _, s := range shapes {
fmt.Printf("%s: %d\n", s.Name(), s.Area())
total += s.Area()
}
fmt.Printf("total: %d\n", total)
}
$ go run main.go
square: 9
rect: 24
total: 33
interface Shape {
fun name(): String
fun area(): Int
}
class Square(val side: Int) : Shape {
override fun name() = "square"
override fun area() = side * side
}
class Rect(val w: Int, val h: Int) : Shape {
override fun name() = "rect"
override fun area() = w * h
}
fun main() {
val shapes: List<Shape> = listOf(Square(3), Rect(4, 6))
var total = 0
for (s in shapes) {
println("${s.name()}: ${s.area()}")
total += s.area()
}
println("total: $total")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
square: 9
rect: 24
total: 33
# No interface to declare: any object with name() and area() will do.
class Square:
def __init__(self, side):
self.side = side
def name(self):
return "square"
def area(self):
return self.side * self.side
class Rect:
def __init__(self, w, h):
self.w = w
self.h = h
def name(self):
return "rect"
def area(self):
return self.w * self.h
shapes = [Square(3), Rect(4, 6)]
total = 0
for s in shapes:
print(f"{s.name()}: {s.area()}")
total += s.area()
print(f"total: {total}")
$ python3 main.py
square: 9
rect: 24
total: 33
interface Shape {
name(): string;
area(): number;
}
class Square implements Shape {
constructor(private side: number) {}
name() { return "square"; }
area() { return this.side * this.side; }
}
class Rect implements Shape {
constructor(private w: number, private h: number) {}
name() { return "rect"; }
area() { return this.w * this.h; }
}
const shapes: Shape[] = [new Square(3), new Rect(4, 6)];
let total = 0;
for (const s of shapes) {
console.log(`${s.name()}: ${s.area()}`);
total += s.area();
}
console.log(`total: ${total}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
square: 9
rect: 24
total: 33
import java.util.List;
interface Shape {
String name();
int area();
}
class Square implements Shape {
private final int side;
Square(int side) { this.side = side; }
public String name() { return "square"; }
public int area() { return side * side; }
}
class Rect implements Shape {
private final int w, h;
Rect(int w, int h) { this.w = w; this.h = h; }
public String name() { return "rect"; }
public int area() { return w * h; }
}
public class Main {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Square(3), new Rect(4, 6));
int total = 0;
for (Shape s : shapes) {
System.out.println(s.name() + ": " + s.area());
total += s.area();
}
System.out.println("total: " + total);
}
}
$ javac Main.java && java Main
square: 9
rect: 24
total: 33
IShape[] shapes = { new Square(3), new Rect(4, 6) };
int total = 0;
foreach (IShape s in shapes)
{
Console.WriteLine($"{s.Name()}: {s.Area()}");
total += s.Area();
}
Console.WriteLine($"total: {total}");
interface IShape
{
string Name();
int Area();
}
class Square(int side) : IShape
{
public string Name() => "square";
public int Area() => side * side;
}
class Rect(int w, int h) : IShape
{
public string Name() => "rect";
public int Area() => w * h;
}
$ dotnet run
square: 9
rect: 24
total: 33
#include <stdio.h>
// C has no traits. A "dyn Shape" is two pointers you carry yourself:
// one to the data and one to a table of functions.
struct ShapeVTable {
const char *(*name)(const void *self);
unsigned (*area)(const void *self);
};
struct Shape {
const void *data;
const struct ShapeVTable *vtable;
};
struct Square { unsigned side; };
struct Rect { unsigned w, h; };
const char *square_name(const void *self) { (void)self; return "square"; }
unsigned square_area(const void *self) {
const struct Square *s = self;
return s->side * s->side;
}
const char *rect_name(const void *self) { (void)self; return "rect"; }
unsigned rect_area(const void *self) {
const struct Rect *r = self;
return r->w * r->h;
}
const struct ShapeVTable SQUARE_VTABLE = {square_name, square_area};
const struct ShapeVTable RECT_VTABLE = {rect_name, rect_area};
int main(void) {
struct Square sq = {3};
struct Rect rect = {4, 6};
struct Shape shapes[] = {{&sq, &SQUARE_VTABLE}, {&rect, &RECT_VTABLE}};
unsigned total = 0;
for (int i = 0; i < 2; i++) {
struct Shape s = shapes[i];
printf("%s: %u\n", s.vtable->name(s.data), s.vtable->area(s.data));
total += s.vtable->area(s.data);
}
printf("total: %u\n", total);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
square: 9
rect: 24
total: 33
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Shape {
public:
virtual ~Shape() = default;
virtual std::string name() const = 0;
virtual unsigned area() const = 0;
};
class Square : public Shape {
public:
explicit Square(unsigned side) : side_(side) {}
std::string name() const override { return "square"; }
unsigned area() const override { return side_ * side_; }
private:
unsigned side_;
};
class Rect : public Shape {
public:
Rect(unsigned w, unsigned h) : w_(w), h_(h) {}
std::string name() const override { return "rect"; }
unsigned area() const override { return w_ * h_; }
private:
unsigned w_, h_;
};
int main() {
// a vector of objects of different classes holds pointers to them
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Square>(3));
shapes.push_back(std::make_unique<Rect>(4, 6));
unsigned total = 0;
for (const auto& s : shapes) {
std::cout << s->name() << ": " << s->area() << "\n";
total += s->area();
}
std::cout << "total: " << total << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
square: 9
rect: 24
total: 33

What's different

  • dyn Shape means "some type that implements Shape, known only at run time". Its size is not known, so you cannot hold one by value. You hold it behind a pointer: &dyn Shape, &mut dyn Shape, or Box<dyn Shape>. A Box is an owned pointer to a value on the heap.
  • A Vec holds values of one type and one size. Box<dyn Shape> is one type and one size whatever it contains, so a Vec<Box<dyn Shape>> can hold squares and rectangles side by side.
  • The call s.area() looks up the right area through a table at run time. That is dynamic dispatch. The compile-time alternative is a generic fn f<T: Shape>(s: &T), which the compiler writes once per type.
  • Only some traits can be used as dyn, because the compiler must be able to build the table. A method with its own type parameter, or one that returns Self, makes the trait unusable as dyn unless that method says where Self: Sized.
Gotcha

A list of a trait, without Box

Looks fine: in Go, Kotlin, Python, TypeScript, Java and C# it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.No direct equivalent in C. Just a Rust rule.C++ refuses this too. Same idea, different words.
Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shapes: Vec<Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
let total: u32 = shapes.iter().map(|s| s.area()).sum();
println!("total: {total}");
}
package main
import "fmt"
type Shape interface{ Area() int }
type Square struct{ Side int }
type Rect struct{ W, H int }
func (s Square) Area() int { return s.Side * s.Side }
func (r Rect) Area() int { return r.W * r.H }
func main() {
shapes := []Shape{Square{3}, Rect{4, 6}}
total := 0
for _, s := range shapes {
total += s.Area()
}
fmt.Println("total:", total)
}
$ go run main.go✓ runs
total: 33
interface Shape {
fun area(): Int
}
class Square(val side: Int) : Shape {
override fun area() = side * side
}
class Rect(val w: Int, val h: Int) : Shape {
override fun area() = w * h
}
fun main() {
val shapes: List<Shape> = listOf(Square(3), Rect(4, 6))
println("total: ${shapes.sumOf { it.area() }}")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
total: 33
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
class Rect:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
shapes = [Square(3), Rect(4, 6)]
print("total:", sum(s.area() for s in shapes))
$ python3 main.py✓ runs
total: 33
interface Shape {
area(): number;
}
class Square implements Shape {
constructor(private side: number) {}
area() { return this.side * this.side; }
}
class Rect implements Shape {
constructor(private w: number, private h: number) {}
area() { return this.w * this.h; }
}
const shapes: Shape[] = [new Square(3), new Rect(4, 6)];
console.log("total:", shapes.reduce((sum, s) => sum + s.area(), 0));
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
total: 33
import java.util.List;
interface Shape {
int area();
}
class Square implements Shape {
private final int side;
Square(int side) { this.side = side; }
public int area() { return side * side; }
}
class Rect implements Shape {
private final int w, h;
Rect(int w, int h) { this.w = w; this.h = h; }
public int area() { return w * h; }
}
public class Main {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Square(3), new Rect(4, 6));
int total = 0;
for (Shape s : shapes) {
total += s.area();
}
System.out.println("total: " + total);
}
}
$ javac Main.java && java Main✓ runs
total: 33
var shapes = new List<IShape> { new Square(3), new Rect(4, 6) };
int total = 0;
foreach (IShape s in shapes)
{
total += s.Area();
}
Console.WriteLine($"total: {total}");
interface IShape
{
int Area();
}
class Square(int side) : IShape
{
public int Area() => side * side;
}
class Rect(int w, int h) : IShape
{
public int Area() => w * h;
}
$ dotnet run✓ runs
total: 33
Cno equivalent

C has no traits. A mixed array is an array of structs holding a data pointer and a function table, as main.c shows.

C++✗ refuses too

C++ refuses this too: an array (or a std::vector) of Shape stores Shape objects themselves, and Shape is abstract. The usual fix is the same as in Rust, pointers: std::vector<std::unique_ptr<Shape>>.

#include <iostream>
class Shape {
public:
virtual ~Shape() = default;
virtual unsigned area() const = 0;
};
class Square : public Shape {
public:
explicit Square(unsigned side) : side_(side) {}
unsigned area() const override { return side_ * side_; }
private:
unsigned side_;
};
class Rect : public Shape {
public:
Rect(unsigned w, unsigned h) : w_(w), h_(h) {}
unsigned area() const override { return w_ * h_; }
private:
unsigned w_, h_;
};
int main() {
Shape shapes[] = {Square(3), Rect(4, 6)};
unsigned total = 0;
for (const Shape& s : shapes) total += s.area();
std::cout << "total: " << total << "\n";
}
main.cpp: In function ‘int main()’:
main.cpp:28:44: error: cannot allocate an object of abstract type ‘Shape’
   28 |     Shape shapes[] = {Square(3), Rect(4, 6)};
      |                                            ^
main.cpp:3:7: note:   because the following virtual functions are pure within ‘Shape’:
    3 | class Shape {
      |       ^~~~~
main.cpp:6:22: note:     ‘virtual unsigned int Shape::area() const’
    6 |     virtual unsigned area() const = 0;
      |                      ^~~~
main.cpp:28:44: error: cannot allocate an object of abstract type ‘Shape’
   28 |     Shape shapes[] = {Square(3), Rect(4, 6)};
      |                                            ^
main.cpp:28:44: error: creating array of ‘Shape’, which is an abstract class type
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0782?
error[E0782]: expected a type, found a trait
  --> src/main.rs:27:21
   |
27 |     let shapes: Vec<Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
   |                     ^^^^^
   |
help: you can add the `dyn` keyword if you want a trait object
   |
27 |     let shapes: Vec<dyn Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
   |                     +++
What happens in Go

Go runs this and prints total: 33. An interface value is always the same size (two words) and holds a copy of the value, so []Shape holds a mix.

What happens in Kotlin

Kotlin runs this and prints total: 33. A List<Shape> holds references, which are all the same size.

What happens in Python

Python prints total: 33. A list holds references to objects, and there is no declared element type to satisfy.

What happens in TypeScript

Prints total: 33. A Shape[] holds references to objects of any class that fits the interface.

What happens in Java

Java runs this and prints total: 33. A List<Shape> holds references, so it holds any mix of implementing classes.

What happens in C#

C# runs this and prints total: 33. A List<IShape> holds references to class instances.

What happens in C

C has no traits. A mixed array is an array of structs holding a data pointer and a function table, as main.c shows.

What happens in C++

C++ refuses this too: an array (or a std::vector) of Shape stores Shape objects themselves, and Shape is abstract. The usual fix is the same as in Rust, pointers: std::vector<std::unique_ptr<Shape>>.

What Rust sees

Shape is a trait, not a type. It names a whole set of types of different sizes, and a Vec needs every element to be the same size. The compiler asks for dyn, and after that it asks for a pointer.

Why Rust says no

Those languages give the list an element of one fixed size: a reference (Kotlin, Java, C#, TypeScript) or, in Go, a two-word interface value that holds a copy of the value and points at it. Rust stores values where you put them and does not add a pointer on its own, so you write the pointer: Box<dyn Shape>.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> =
vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })];
let total: u32 = shapes.iter().map(|s| s.area()).sum();
println!("total: {total}");
}
$ cargo run
total: 33

The toolkit

A value whose type you have forgotten

A trait object is a value of some type that implements a trait, where the code no longer knows which type. You write its type as dyn Shape. A call like s.area() is looked up at run time through a table of the type’s methods (a vtable). That is dynamic dispatch, what interface values do in most languages.

The same trait can be held three ways:

Rustsrc/main.rsRun ↗
trait Shape {
fn name(&self) -> String;
fn area(&self) -> u32;
fn scale(&mut self, by: u32);
// a default method works through a dyn too
fn describe(&self) -> String {
format!("{} with area {}", self.name(), self.area())
}
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn name(&self) -> String {
String::from("square")
}
fn area(&self) -> u32 {
self.side * self.side
}
fn scale(&mut self, by: u32) {
self.side *= by;
}
}
impl Shape for Rect {
fn name(&self) -> String {
String::from("rect")
}
fn area(&self) -> u32 {
self.w * self.h
}
fn scale(&mut self, by: u32) {
self.w *= by;
self.h *= by;
}
}
// borrowed, read-only: any Shape, behind a shared reference
fn show(s: &dyn Shape) {
println!("show: {}", s.describe());
}
// borrowed, changeable: the same, behind a &mut
fn double(s: &mut dyn Shape) {
s.scale(2);
}
// owned: a Box owns a heap value; the list owns its boxes
fn total(shapes: &[Box<dyn Shape>]) -> u32 {
shapes.iter().map(|s| s.area()).sum()
}
fn main() {
let mut sq = Square { side: 3 };
show(&sq);
double(&mut sq);
show(&sq);
// &dyn needs no heap: two references to values in main's own frame
let rect = Rect { w: 4, h: 6 };
let on_stack: [&dyn Shape; 2] = [&sq, &rect];
println!("on the stack: {} shapes", on_stack.len());
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(sq), Box::new(rect)];
for s in &shapes {
println!("list: {}", s.describe());
}
println!("total area: {}", total(&shapes));
// two ways to borrow the shape inside a box as a plain &dyn Shape
show(shapes[1].as_ref());
show(&*shapes[0]);
}
$ cargo run
show: square with area 9
show: square with area 36
on the stack: 2 shapes
list: square with area 36
list: rect with area 24
total area: 60
show: rect with area 24
show: square with area 36
  • &dyn Shape borrows any Shape for reading. &sq is a &Square, and Rust turns it into a &dyn Shape where the signature asks for one. It needs no heap: on_stack holds references to values in main’s own frame.
  • &mut dyn Shape borrows it for changing. double scaled the same sq that show printed.
  • Box<dyn Shape> owns it. A Box is an owned pointer to a value on the heap, freed when the box is dropped (Box gets its own lesson, Smart pointers). Box::new(sq) moved sq onto the heap, and the Vec owns the boxes.

Default methods like describe work through a dyn too. Through a dyn Shape you can call the trait’s methods and those of its supertraits (traits it requires, written trait Shape: Debug; see Traits), and nothing of the concrete type’s own. The Rect is still in there, but its fields are out of reach:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shape: Box<dyn Shape> = Box::new(Rect { w: 4, h: 6 });
println!("area: {}", shape.area());
println!("width: {}", shape.w);
}
$ cargo buildWhat is E0609?
error[E0609]: no field `w` on type `Box<dyn Shape>`
  --> src/main.rs:29:33
   |
29 |     println!("width: {}", shape.w);
   |                                 ^ unknown field

Which one to use is the usual question: borrow to read, borrow to change, or own. Shapes with several owners are Rc<dyn Shape> or Arc<dyn Shape> (smart pointers).

To hand a boxed shape to a function that wants &dyn Shape, &boxed is not enough:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn show(s: &dyn Shape) {
println!("area: {}", s.area());
}
fn main() {
let boxed: Box<dyn Shape> = Box::new(Rect { w: 4, h: 6 });
show(&boxed);
}
$ cargo buildWhat is E0277?
error[E0277]: the trait bound `Box<dyn Shape>: Shape` is not satisfied
  --> src/main.rs:32:10
   |
32 |     show(&boxed);
   |          ^^^^^^ the trait `Shape` is not implemented for `Box<dyn Shape>`
   |
help: the following other types implement trait `Shape`
  --> src/main.rs:14:1
   |
14 | impl Shape for Square {
   | ^^^^^^^^^^^^^^^^^^^^^ `Square`
...
20 | impl Shape for Rect {
   | ^^^^^^^^^^^^^^^^^^^ `Rect`
   = note: required for the cast from `&Box<dyn Shape>` to `&dyn Shape`

The compiler treats the whole Box<dyn Shape> as the shape, and a box does not implement Shape. Borrow what is inside it: boxed.as_ref() or &*boxed.

Why not a Vec<Shape>?

The gotcha near the top of the page tried that, and the compiler said “expected a type, found a trait”. A trait names a set of types, not one. Follow the compiler’s advice and write dyn Shape:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shapes: Vec<dyn Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
println!("count: {}", shapes.len());
}
$ cargo buildWhat is E0277?
error[E0277]: the size for values of type `dyn Shape` cannot be known at compilation time
  --> src/main.rs:27:17
   |
27 |     let shapes: Vec<dyn Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
   |                 ^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn Shape`
  ... (notes pointing into the standard library trimmed)

error[E0308]: mismatched types
  --> src/main.rs:27:59
   |
27 |     let shapes: Vec<dyn Shape> = vec![Square { side: 3 }, Rect { w: 4, h: 6 }];
   |                                                           ^^^^^^^^^^^^^^^^^^^ expected `Square`, found `Rect`

Some errors have detailed explanations: E0277, E0308.

There are two errors. A Vec<T> stores items side by side, so every T needs a size known at compile time, and a dyn Shape might be 4 bytes or much more. And the vec! is refused, because all items of a Vec have one type. A Box<dyn Shape> is one type of one size.

The check happens where the box is made. A type that does not implement the trait never gets in:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
struct Rock;
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square { side: 3 }), Box::new(Rock)];
println!("count: {}", shapes.len());
}
$ cargo buildWhat is E0277?
error[E0277]: the trait bound `Rock: Shape` is not satisfied
  --> src/main.rs:29:74
   |
29 |     let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square { side: 3 }), Box::new(Rock)];
   |                                                                          ^^^^^^^^^^^^^^ unsatisfied trait bound
   |
help: the trait `Shape` is not implemented for `Rock`
  --> src/main.rs:26:1
   |
26 | struct Rock;
   | ^^^^^^^^^^^
help: the following other types implement trait `Shape`
  --> src/main.rs:14:1
   |
14 | impl Shape for Square {
   | ^^^^^^^^^^^^^^^^^^^^^ `Square`
...
20 | impl Shape for Rect {
   | ^^^^^^^^^^^^^^^^^^^ `Rect`
   = note: required for the cast from `Box<Rock>` to `Box<dyn Shape>`

Where you have already met dyn

Rustsrc/main.rsRun ↗
use std::error::Error;
use std::io::Write;
fn parse(text: &str) -> Result<i32, Box<dyn Error>> {
Ok(text.trim().parse::<i32>()?)
}
fn evens_or_all(only_even: bool) -> Box<dyn Iterator<Item = u32>> {
if only_even {
Box::new((1..=6).filter(|n| n % 2 == 0))
} else {
Box::new(1..=3)
}
}
fn log_to(out: &mut dyn Write, msg: &str) {
writeln!(out, "log: {msg}").unwrap();
}
fn main() {
println!("parse(\" 42 \"): {:?}", parse(" 42 ").ok());
println!("parse(\"x\"): {}", parse("x").unwrap_err());
println!("evens: {:?}", evens_or_all(true).collect::<Vec<_>>());
println!("first three: {:?}", evens_or_all(false).collect::<Vec<_>>());
let offset = 10;
let steps: Vec<Box<dyn Fn(i32) -> i32>> = vec![
Box::new(|x| x + 1),
Box::new(|x| x * 2),
Box::new(move |x| x + offset),
];
println!(
"closures: {:?}",
steps.iter().map(|f| f(5)).collect::<Vec<_>>()
);
let mut buffer: Vec<u8> = Vec::new();
log_to(&mut buffer, "into a Vec");
log_to(&mut std::io::stdout(), "to the terminal");
println!("buffer holds {} bytes", buffer.len());
}
$ cargo run
parse(" 42 "): Some(42)
parse("x"): invalid digit found in string
evens: [2, 4, 6]
first three: [1, 2, 3]
closures: [6, 10, 15]
log: to the terminal
buffer holds 16 bytes
You write It means
Box<dyn Error> any error type (Errors)
Box<dyn Iterator<Item = u32>> an iterator whose type depends on a branch (Iterators)
Box<dyn Fn(i32) -> i32> a closure. Every closure has its own type, so a list of different closures needs this (Closures)
&mut dyn Write anywhere bytes can go: a Vec<u8>, a file, the terminal

Generics or dyn?

The traits lesson compared the two. Facts this page can print:

Rustsrc/main.rsRun ↗
use std::any::{type_name, type_name_of_val};
use std::mem::size_of_val;
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn show_generic<T: Shape>(s: &T) {
println!("generic: T is {}, area {}", type_name::<T>(), s.area());
}
fn show_dyn(s: &dyn Shape) {
println!(
"dyn: type is {}, area {}, real size {}",
type_name_of_val(s),
s.area(),
size_of_val(s)
);
}
fn main() {
let sq = Square { side: 3 };
let rect = Rect { w: 4, h: 6 };
show_generic(&sq);
show_generic(&rect);
show_dyn(&sq);
show_dyn(&rect);
let for_square = show_generic::<Square> as *const ();
let for_rect = show_generic::<Rect> as *const ();
println!("two copies of show_generic: {}", for_square != for_rect);
}
$ cargo run
generic: T is main::Square, area 9
generic: T is main::Rect, area 24
dyn: type is dyn main::Shape, area 9, real size 4
dyn: type is dyn main::Shape, area 24, real size 8
two copies of show_generic: true
  • show_generic<T> is compiled once per type it is used with (Generics): two functions in this build.
  • show_dyn is compiled once. The type is dyn Shape for every caller, and the real size (4, then 8) is found through the pointer.

A call through the table is an indirect call, and the Rust Book notes that it prevents inlining the method. That alone does not make dyn slow. A JIT (the JVM’s, .NET’s) can inline an interface call from what it has seen while running. Rust compiles ahead of time with no such profile, so the source decides. Whether it matters is a measurement to make with cargo run --release; this page times nothing, because its runner builds only debug programs (Hello, World).

Which to pick. Default to a generic. Use dyn when a collection or field must hold several types, or the type is only known at run time. Two costs sit behind that default:

  • A type parameter spreads. A struct holding a Canvas<S> must carry S too, and so must the struct holding that; App<Square> and App<Rect> are different types. A Box<dyn Shape> field keeps the type out of every signature. That is a common reason for dyn in struct fields.
  • One copy per type is more code. Each type adds a copy of the generic function (two copies of show_generic: true). dyn has one. This page measures neither code size nor compile time.
Rustsrc/main.rsRun ↗
use std::any::type_name;
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
// generic: the type parameter has to be named by everything that holds a Canvas
struct Canvas<S: Shape> {
shapes: Vec<S>,
}
struct App<S: Shape> {
canvas: Canvas<S>,
}
// dyn: the shapes' types are not part of the type
struct DynCanvas {
shapes: Vec<Box<dyn Shape>>,
}
struct DynApp {
canvas: DynCanvas,
}
fn main() {
let a = App {
canvas: Canvas {
shapes: vec![Square { side: 3 }],
},
};
let b = App {
canvas: Canvas {
shapes: vec![Rect { w: 4, h: 6 }],
},
};
println!("generic a: {}", type_name::<App<Square>>());
println!("generic b: {}", type_name::<App<Rect>>());
println!("areas: {} and {}", a.canvas.shapes[0].area(), b.canvas.shapes[0].area());
let c = DynApp {
canvas: DynCanvas {
shapes: vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })],
},
};
println!("dyn: {}", type_name::<DynApp>());
println!("dyn areas: {} and {}", c.canvas.shapes[0].area(), c.canvas.shapes[1].area());
}
$ cargo run
generic a: main::App<main::Square>
generic b: main::App<main::Rect>
areas: 9 and 24
dyn: main::DynApp
dyn areas: 9 and 24

What dyn buys is flexibility. -> impl Shape names one type (Traits shows the error); when the type depends on data, Box<dyn Shape> can return either. That is plugin-like code: a name from a config file, and one function that hands back the matching implementation.

Rustsrc/main.rsRun ↗
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn make(kind: &str) -> Option<Box<dyn Shape>> {
match kind {
"square" => Some(Box::new(Square { side: 3 })),
"rect" => Some(Box::new(Rect { w: 4, h: 6 })),
_ => None,
}
}
fn main() {
for kind in ["square", "rect", "circle"] {
match make(kind) {
Some(shape) => println!("{kind}: area {}", shape.area()),
None => println!("{kind}: unknown shape"),
}
}
}
$ cargo run
square: area 9
rect: area 24
circle: unknown shape

If you write all the types and the list is closed, a third option is an enum, with no pointer and no table. The traits lesson compares enum and trait; this table adds the generic column.

Rustsrc/main.rsRun ↗
use std::mem::size_of;
enum Shape {
Square { side: u32 },
Rect { w: u32, h: u32 },
}
impl Shape {
fn area(&self) -> u32 {
match self {
Shape::Square { side } => side * side,
Shape::Rect { w, h } => w * h,
}
}
}
fn main() {
let shapes = vec![Shape::Square { side: 3 }, Shape::Rect { w: 4, h: 6 }];
for s in &shapes {
match s {
Shape::Rect { w, h } => println!("a Rect, {w} wide and {h} high"),
Shape::Square { .. } => println!("not a Rect (area {})", s.area()),
}
}
println!("size_of Shape (the enum): {}", size_of::<Shape>());
}
$ cargo run
not a Rect (area 9)
a Rect, 4 wide and 6 high
size_of Shape (the enum): 12
generic T: Shape dyn Shape enum
which types one per call, fixed at compile time any, chosen at run time the variants you listed
a Vec of mixed types no yes, as Vec<Box<dyn Shape>> yes, stored inline
a new type from another module or crate yes yes no: the enum must change
the call is found at compile time through the table by match
one list item takes its own size 16 bytes, plus the value on the heap the largest variant plus a tag (12 here)

Translating from what you know

Going deeper

What a dyn pointer is made of

A &dyn Shape or Box<dyn Shape> is a fat pointer: two machine words, the address of the value and the address of the vtable for its type. A pointer to a sized type is one word. The sizes here are for this 64-bit Linux runner, where a word is 8 bytes.

Rustsrc/main.rsRun ↗
use std::mem::{align_of_val, size_of, size_of_val};
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
println!("size_of Square: {}", size_of::<Square>());
println!("size_of Rect: {}", size_of::<Rect>());
println!("size_of &Rect: {}", size_of::<&Rect>());
println!("size_of &dyn Shape: {}", size_of::<&dyn Shape>());
println!("size_of Box<Rect>: {}", size_of::<Box<Rect>>());
println!("size_of Box<dyn Shape>: {}", size_of::<Box<dyn Shape>>());
println!(
"size_of Option<Box<dyn Shape>>: {}",
size_of::<Option<Box<dyn Shape>>>()
);
println!("size_of &[u32]: {}", size_of::<&[u32]>());
println!("size_of &str: {}", size_of::<&str>());
let shapes: Vec<Box<dyn Shape>> =
vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })];
for (i, s) in shapes.iter().enumerate() {
let shape: &dyn Shape = &**s;
println!(
"shapes[{i}]: area {}, size_of_val {}, align_of_val {}",
shape.area(),
size_of_val(shape),
align_of_val(shape)
);
}
let sq = Square { side: 3 };
let plain: &Square = &sq;
let erased: &dyn Shape = plain;
let same = std::ptr::addr_eq(plain as *const Square, erased as *const dyn Shape);
println!("same data address: {same} (area {})", erased.area());
}
$ cargo run
size_of Square: 4
size_of Rect: 8
size_of &Rect: 8
size_of &dyn Shape: 16
size_of Box<Rect>: 8
size_of Box<dyn Shape>: 16
size_of Option<Box<dyn Shape>>: 16
size_of &[u32]: 16
size_of &str: 16
shapes[0]: area 9, size_of_val 4, align_of_val 4
shapes[1]: area 24, size_of_val 8, align_of_val 4
same data address: true (area 9)
let a: Box<dyn Shape> = Box::new(Square { side: 3 }); let b: Box<dyn Shape> = Box::new(Rect { w: 4, h: 6 });
STACKHEAPa: Box<dyn Shape>data ptrvtable ptrthe vtable pointer leads to the table for Square as a Shapeb: Box<dyn Shape>data ptrvtable ptrthe vtable pointer leads to the table for Rect as a Shapea Square (4 bytes)side 3a Rect (8 bytes)w 4h 6
Both boxes are 16 bytes, so a Vec of them has equal-sized items. In practice the vtables sit in read-only static data, not on the heap, so they are not drawn.

What the printed facts say:

  • Box<Rect> is 8 bytes and Box<dyn Shape> is 16. The extra word is the vtable pointer.
  • The pointer inside the fat pointer is the same address the plain reference had (same data address: true). Turning a &Square into a &dyn Shape adds a word and changes nothing else.
  • Option<Box<dyn Shape>> is still 16: a box is never null, so None takes that spot.
  • size_of_val returns 4 for the square and 8 for the rect, from the same Box<dyn Shape> type. The real size and alignment must travel with the pointer.

The Reference says a vtable holds a pointer to each method’s implementation, supertraits included. The sizes above and the drop below need more, and in practice rustc also keeps drop, size and alignment in the table. The Reference leaves the rest unspecified and says not to rely on two-word pointers, though they are today.

Rustsrc/main.rsRun ↗
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
struct Noisy(&'static str);
impl Shape for Noisy {
fn area(&self) -> u32 {
self.0.len() as u32
}
}
impl Drop for Noisy {
fn drop(&mut self) {
println!("dropping {}", self.0);
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Noisy("first")),
Box::new(Square { side: 2 }),
Box::new(Noisy("third")),
Box::new(Rect { w: 1, h: 1 }),
];
println!(
"areas: {:?}",
shapes.iter().map(|s| s.area()).collect::<Vec<_>>()
);
drop(shapes);
println!("done");
}
$ cargo run
areas: [5, 4, 5, 1]
dropping first
dropping third
done

Each box ran the drop code of the type it holds, in list order.

Supertraits come along

A trait object has the trait’s methods and those of its supertraits. trait Shape: Debug means every Shape is also Debug, so {:?} works on a Box<dyn Shape>. This is how a dyn gets the job of toString():

Rustsrc/main.rsRun ↗
use std::fmt::Debug;
trait Shape: Debug {
fn area(&self) -> u32;
}
#[derive(Debug)]
struct Square {
side: u32,
}
#[derive(Debug)]
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> =
vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })];
println!("{shapes:?}");
for s in &shapes {
println!("{s:?} has area {}", s.area());
}
}
$ cargo run
[Square { side: 3 }, Rect { w: 4, h: 6 }]
Square { side: 3 } has area 9
Rect { w: 4, h: 6 } has area 24

Which traits can be dyn

The compiler must be able to build a table with one function pointer per method. Some methods cannot have one:

Rustsrc/main.rs✗ won't compile
use std::fmt::Debug;
trait Shape {
fn area(&self) -> u32;
fn doubled(&self) -> Self;
fn describe<T: Debug>(&self, extra: T) -> String;
}
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
fn doubled(&self) -> Self {
Square {
side: self.side * 2,
}
}
fn describe<T: Debug>(&self, extra: T) -> String {
format!("square {} ({extra:?})", self.side)
}
}
fn main() {
let shape: Box<dyn Shape> = Box::new(Square { side: 3 });
println!("area: {}", shape.area());
}
$ cargo buildWhat is E0038?
error[E0038]: the trait `Shape` is not dyn compatible
  --> src/main.rs:28:24
   |
28 |     let shape: Box<dyn Shape> = Box::new(Square { side: 3 });
   |                        ^^^^^ `Shape` is not dyn compatible
   |
note: for a trait to be dyn compatible it needs to allow building a vtable
      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
  --> src/main.rs:5:26
   |
 3 | trait Shape {
   |       ----- this trait is not dyn compatible...
 4 |     fn area(&self) -> u32;
 5 |     fn doubled(&self) -> Self;
   |                          ^^^^ ...because method `doubled` references the `Self` type in its return type
 6 |     fn describe<T: Debug>(&self, extra: T) -> String;
   |        ^^^^^^^^ ...because method `describe` has generic type parameters
   = help: consider moving `doubled` to another trait
   = help: consider moving `describe` to another trait
   = help: only type `Square` implements `Shape`; consider using it directly instead.

The error names both reasons. A method returning Self has no fixed return type through a dyn. A method with its own type parameter would need a table entry for every type it is called with. The Reference lists the full rules (“dyn compatible” was called “object safe”), such as no Sized supertrait (Sized marks a size known at compile time) and no function without self, like fn new() -> Self.

A method can stay if it says it needs a concrete type:

Rustsrc/main.rsRun ↗
use std::fmt::Debug;
trait Shape {
fn area(&self) -> u32;
fn doubled(&self) -> Self
where
Self: Sized;
fn describe<T: Debug>(&self, extra: T) -> String
where
Self: Sized;
}
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
fn doubled(&self) -> Self {
Square {
side: self.side * 2,
}
}
fn describe<T: Debug>(&self, extra: T) -> String {
format!("square {} ({extra:?})", self.side)
}
}
fn main() {
let sq = Square { side: 3 };
println!("on the concrete type: {}", sq.describe("hi"));
println!("doubled area: {}", sq.doubled().area());
let shape: Box<dyn Shape> = Box::new(sq);
println!("through dyn: area {}", shape.area());
}
$ cargo run
on the concrete type: square 3 ("hi")
doubled area: 36
through dyn: area 9

where Self: Sized removes the method from the table. It works on sq, the concrete type, and is refused through the box:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
fn doubled(&self) -> Self
where
Self: Sized;
}
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
fn doubled(&self) -> Self {
Square {
side: self.side * 2,
}
}
}
fn main() {
let shape: Box<dyn Shape> = Box::new(Square { side: 3 });
let bigger = shape.doubled();
println!("area: {}", bigger.area());
}
$ cargo build
error: the `doubled` method cannot be invoked on a trait object
  --> src/main.rs:25:24
   |
 5 |         Self: Sized;
   |               ----- this has a `Sized` requirement
...
25 |     let bigger = shape.doubled();
   |                        ^^^^^^^

Why a Box<dyn Shape> is not Clone

derive(Clone) needs every field to be Clone. A Box<dyn Shape> is Clone only if dyn Shape is, and Shape does not say so:

Rustsrc/main.rs✗ won't compile
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
#[derive(Clone)]
struct Scene {
shapes: Vec<Box<dyn Shape>>,
}
fn main() {
let scene = Scene {
shapes: vec![Box::new(Square { side: 3 })],
};
let copy = scene.clone();
println!("copied {} shapes", copy.shapes.len());
}
$ cargo buildWhat is E0277?
error[E0277]: the trait bound `dyn Shape: Clone` is not satisfied
  --> src/main.rs:17:5
   |
15 | #[derive(Clone)]
   |          ----- in this derive macro expansion
16 | struct Scene {
17 |     shapes: Vec<Box<dyn Shape>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `dyn Shape`
   |
   = note: required for `Box<dyn Shape>` to implement `Clone`
   = note: 1 redundant requirement hidden
   = note: required for `Vec<Box<dyn Shape>>` to implement `Clone`

Adding trait Shape: Clone does not help. Clone requires a sized type, and a Sized supertrait makes the trait not dyn compatible, so it could no longer be dyn:

Rustsrc/main.rs✗ won't compile
trait Shape: Clone {
fn area(&self) -> u32;
}
#[derive(Clone)]
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square { side: 3 })];
println!("count: {}", shapes.len());
}
$ cargo buildWhat is E0038?
error[E0038]: the trait `Shape` is not dyn compatible
  --> src/main.rs:17:29
   |
17 |     let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square { side: 3 })];
   |                             ^^^^^ `Shape` is not dyn compatible
   |
note: for a trait to be dyn compatible it needs to allow building a vtable
      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
  --> src/main.rs:1:14
   |
 1 | trait Shape: Clone {
   |       -----  ^^^^^ ...because it requires `Self: Sized`
   |       |
   |       this trait is not dyn compatible...
   = help: only type `Square` implements `Shape`; consider using it directly instead.

The usual workaround is a method that returns a new box, which every type implements by cloning itself, plus an impl Clone for the box:

Rustsrc/main.rsRun ↗
trait Shape {
fn area(&self) -> u32;
fn grow(&mut self);
fn clone_box(&self) -> Box<dyn Shape>;
}
#[derive(Clone)]
struct Square {
side: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
fn grow(&mut self) {
self.side += 1;
}
fn clone_box(&self) -> Box<dyn Shape> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn Shape> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[derive(Clone)]
struct Scene {
shapes: Vec<Box<dyn Shape>>,
}
fn main() {
let mut scene = Scene {
shapes: vec![Box::new(Square { side: 3 })],
};
let copy = scene.clone();
scene.shapes[0].grow();
println!("original area: {}", scene.shapes[0].area());
println!("copy area: {}", copy.shapes[0].area());
}
$ cargo run
original area: 16
copy area: 9

The copy kept the original value when the original grew. The dyn-clone crate packages this pattern.

Bounds on the box

A trait object type can carry more bounds, as in Box<dyn Shape + Send> (Send marks types that may move to another thread; see dyn Trait + Send) or Box<dyn Shape + 'static>. You meet them when the compiler asks: Lifetimes teaches 'static, and Threads is where thread::spawn comes in.

Getting the real type back: Any

A dyn Shape has forgotten its type on purpose. If you need it back, Any can answer “is this a Rect?”. It works for types that hold no short-lived borrows ('static, per the std docs). Make Any a supertrait of your trait, and a &dyn Shape can be turned into a &dyn Any:

Rustsrc/main.rsRun ↗
use std::any::Any;
trait Shape: Any {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> =
vec![Box::new(Square { side: 3 }), Box::new(Rect { w: 4, h: 6 })];
for s in &shapes {
// a Shape is also an Any, because Shape: Any
let any: &dyn Any = s.as_ref();
match any.downcast_ref::<Rect>() {
Some(r) => println!("a Rect, {} wide and {} high", r.w, r.h),
None => println!("not a Rect (area {})", s.area()),
}
}
// the trap: this asks about the Box itself, not about what it holds
let boxed_itself: &dyn Any = &shapes[1];
println!("the Box is a Rect: {}", boxed_itself.is::<Rect>());
println!(
"the Box is a Box<dyn Shape>: {}",
boxed_itself.is::<Box<dyn Shape>>()
);
}
$ cargo run
not a Rect (area 9)
a Rect, 4 wide and 6 high
the Box is a Rect: false
the Box is a Box<dyn Shape>: true

downcast_ref::<Rect>() returns Some only for a Rect; is::<Rect>() is the yes-or-no version.

In the last two lines, coercing the &Box<dyn Shape> itself asks about the box, and the answer for Rect is false. Take the shape out of the box first, as as_ref() does.

Needing a downcast often means the types are a closed list, where an enum with match (the table above) is the better fit. For an open set, such as plugins or error types, a downcast is normal.

Trait objects ask for more typing than the languages above: Box::new at each insertion, clone_box for copies, no fields in a trait. In return a Square stays 4 bytes whether or not a dyn ever points at it, as in Go and unlike a C++ class with virtual methods.

Practice

Try it

Each exercise has checks in main. Replace the todo!() and run until it prints all checks passed.

Shapes from text

Write parse_shape, which turns "square 3" or "rect 4 6" into a boxed shape, and None for anything else (a missing or extra number, a word where a number goes, an unknown shape, empty text). Then write largest, which returns the biggest shape as a &dyn Shape; on a tie, the first. The dyn parts are the boxing and the borrow back out of the box. Careful: max_by_key returns the last.

Starterexercises/shape-factory.rs
trait Shape {
fn name(&self) -> String;
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn name(&self) -> String {
String::from("square")
}
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn name(&self) -> String {
String::from("rect")
}
fn area(&self) -> u32 {
self.w * self.h
}
}
// "square 3" and "rect 4 6" become shapes; anything else is None
fn parse_shape(spec: &str) -> Option<Box<dyn Shape>> {
todo!("split the text; build a Square or a Rect and box it")
}
// the shape with the largest area; on a tie, the first one
fn largest(shapes: &[Box<dyn Shape>]) -> Option<&dyn Shape> {
todo!("walk the list, keeping the best so far")
}
fn main() {
assert_eq!(parse_shape("square 3").map(|s| s.area()), Some(9));
assert_eq!(parse_shape("rect 4 6").map(|s| s.area()), Some(24));
assert_eq!(
parse_shape(" rect 4 6 ").map(|s| s.name()),
Some("rect".to_string())
);
// anything that is not a whole shape is None
assert!(parse_shape("rect 4").is_none());
assert!(parse_shape("rect 4 6 7").is_none());
assert!(parse_shape("square 3 4").is_none());
assert!(parse_shape("square x").is_none());
assert!(parse_shape("square -1").is_none());
assert!(parse_shape("hexagon 2").is_none());
assert!(parse_shape("").is_none());
let none: Vec<Box<dyn Shape>> = Vec::new();
assert!(largest(&none).is_none());
let mixed: Vec<Box<dyn Shape>> = ["square 1", "rect 2 5", "square 3"]
.iter()
.filter_map(|s| parse_shape(s))
.collect();
assert_eq!(largest(&mixed).map(|s| s.name()), Some("rect".to_string()));
// a tie goes to the first
let tie: Vec<Box<dyn Shape>> = ["square 2", "rect 1 4"]
.iter()
.filter_map(|s| parse_shape(s))
.collect();
assert_eq!(largest(&tie).map(|s| s.name()), Some("square".to_string()));
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/shape-factory.solution.rsRun ↗
trait Shape {
fn name(&self) -> String;
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn name(&self) -> String {
String::from("square")
}
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn name(&self) -> String {
String::from("rect")
}
fn area(&self) -> u32 {
self.w * self.h
}
}
// "square 3" and "rect 4 6" become shapes; anything else is None
fn parse_shape(spec: &str) -> Option<Box<dyn Shape>> {
let parts: Vec<&str> = spec.split_whitespace().collect();
match parts.as_slice() {
["square", side] => Some(Box::new(Square {
side: side.parse().ok()?,
})),
["rect", w, h] => Some(Box::new(Rect {
w: w.parse().ok()?,
h: h.parse().ok()?,
})),
_ => None,
}
}
// the shape with the largest area; on a tie, the first one
fn largest(shapes: &[Box<dyn Shape>]) -> Option<&dyn Shape> {
let mut best: Option<&dyn Shape> = None;
for shape in shapes {
match best {
Some(b) if b.area() >= shape.area() => {}
_ => best = Some(shape.as_ref()),
}
}
best
}
fn main() {
assert_eq!(parse_shape("square 3").map(|s| s.area()), Some(9));
assert_eq!(parse_shape("rect 4 6").map(|s| s.area()), Some(24));
assert_eq!(
parse_shape(" rect 4 6 ").map(|s| s.name()),
Some("rect".to_string())
);
// anything that is not a whole shape is None
assert!(parse_shape("rect 4").is_none());
assert!(parse_shape("rect 4 6 7").is_none());
assert!(parse_shape("square 3 4").is_none());
assert!(parse_shape("square x").is_none());
assert!(parse_shape("square -1").is_none());
assert!(parse_shape("hexagon 2").is_none());
assert!(parse_shape("").is_none());
let none: Vec<Box<dyn Shape>> = Vec::new();
assert!(largest(&none).is_none());
let mixed: Vec<Box<dyn Shape>> = ["square 1", "rect 2 5", "square 3"]
.iter()
.filter_map(|s| parse_shape(s))
.collect();
assert_eq!(largest(&mixed).map(|s| s.name()), Some("rect".to_string()));
// a tie goes to the first
let tie: Vec<Box<dyn Shape>> = ["square 2", "rect 1 4"]
.iter()
.filter_map(|s| parse_shape(s))
.collect();
assert_eq!(largest(&tie).map(|s| s.name()), Some("square".to_string()));
println!("all checks passed");
}
$ cargo run
all checks passed

A command registry

A Registry maps names to boxed commands. Commands change when they run (a counter counts), so run them through &mut. Look a command up by name and run it; an unknown name is None, not a panic; registering a name twice replaces the old command.

Starterexercises/registry.rs
use std::collections::HashMap;
trait Command {
fn run(&mut self, arg: &str) -> String;
}
struct Shout;
struct Counter {
calls: u32,
}
struct Repeat {
times: usize,
}
impl Command for Shout {
fn run(&mut self, arg: &str) -> String {
arg.to_uppercase()
}
}
impl Command for Counter {
fn run(&mut self, _arg: &str) -> String {
self.calls += 1;
format!("call {}", self.calls)
}
}
impl Command for Repeat {
fn run(&mut self, arg: &str) -> String {
arg.repeat(self.times)
}
}
struct Registry {
commands: HashMap<String, Box<dyn Command>>,
}
impl Registry {
fn new() -> Registry {
Registry {
commands: HashMap::new(),
}
}
fn register(&mut self, name: &str, command: Box<dyn Command>) {
todo!("store the box under the name")
}
fn run(&mut self, name: &str, arg: &str) -> Option<String> {
todo!("find the command by name and run it with arg; None if there is no such name")
}
}
fn main() {
let mut reg = Registry::new();
reg.register("shout", Box::new(Shout));
reg.register("count", Box::new(Counter { calls: 0 }));
reg.register("twice", Box::new(Repeat { times: 2 }));
assert_eq!(reg.run("shout", "hi"), Some("HI".to_string()));
assert_eq!(reg.run("twice", "ab"), Some("abab".to_string()));
// a command keeps its own state between calls
assert_eq!(reg.run("count", ""), Some("call 1".to_string()));
assert_eq!(reg.run("count", ""), Some("call 2".to_string()));
// an unknown name is None, not a panic
assert_eq!(reg.run("nope", "x"), None);
// registering a name again replaces the old command, state and all
reg.register("count", Box::new(Counter { calls: 10 }));
assert_eq!(reg.run("count", ""), Some("call 11".to_string()));
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/registry.solution.rsRun ↗
use std::collections::HashMap;
trait Command {
fn run(&mut self, arg: &str) -> String;
}
struct Shout;
struct Counter {
calls: u32,
}
struct Repeat {
times: usize,
}
impl Command for Shout {
fn run(&mut self, arg: &str) -> String {
arg.to_uppercase()
}
}
impl Command for Counter {
fn run(&mut self, _arg: &str) -> String {
self.calls += 1;
format!("call {}", self.calls)
}
}
impl Command for Repeat {
fn run(&mut self, arg: &str) -> String {
arg.repeat(self.times)
}
}
struct Registry {
commands: HashMap<String, Box<dyn Command>>,
}
impl Registry {
fn new() -> Registry {
Registry {
commands: HashMap::new(),
}
}
fn register(&mut self, name: &str, command: Box<dyn Command>) {
self.commands.insert(name.to_string(), command);
}
fn run(&mut self, name: &str, arg: &str) -> Option<String> {
let command = self.commands.get_mut(name)?;
Some(command.run(arg))
}
}
fn main() {
let mut reg = Registry::new();
reg.register("shout", Box::new(Shout));
reg.register("count", Box::new(Counter { calls: 0 }));
reg.register("twice", Box::new(Repeat { times: 2 }));
assert_eq!(reg.run("shout", "hi"), Some("HI".to_string()));
assert_eq!(reg.run("twice", "ab"), Some("abab".to_string()));
// a command keeps its own state between calls
assert_eq!(reg.run("count", ""), Some("call 1".to_string()));
assert_eq!(reg.run("count", ""), Some("call 2".to_string()));
// an unknown name is None, not a panic
assert_eq!(reg.run("nope", "x"), None);
// registering a name again replaces the old command, state and all
reg.register("count", Box::new(Counter { calls: 10 }));
assert_eq!(reg.run("count", ""), Some("call 11".to_string()));
println!("all checks passed");
}
$ cargo run
all checks passed

Generic or dyn?

total_of adds up a slice of items that all have one type: add the bound it lacks. total_mixed adds up references to values of different types in the caller’s frame, with no Box. The checks call each as its situation needs, so the wrong tool does not compile.

Starterexercises/choose.rs
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
// every item has the same type, and the caller knows it: a generic (the bound is missing)
fn total_of<T>(items: &[T]) -> u32 {
todo!("add the bound, then add up the areas")
}
// items of different types, borrowed from the caller's stack, no Box: a trait object
fn total_mixed(items: &[&dyn Shape]) -> u32 {
todo!("add up the areas")
}
fn main() {
let squares = [Square { side: 1 }, Square { side: 2 }];
let rects = vec![Rect { w: 1, h: 2 }, Rect { w: 3, h: 4 }];
assert_eq!(total_of(&squares), 5);
assert_eq!(total_of(&rects), 14);
let nothing: [Square; 0] = [];
assert_eq!(total_of(&nothing), 0);
let sq = Square { side: 3 };
let rect = Rect { w: 4, h: 6 };
assert_eq!(total_mixed(&[&sq, &rect]), 33);
assert_eq!(total_mixed(&[&rect]), 24);
assert_eq!(total_mixed(&[]), 0);
// nothing was moved: the values are still ours
assert_eq!(sq.area() + rect.area(), 33);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/choose.solution.rsRun ↗
trait Shape {
fn area(&self) -> u32;
}
struct Square {
side: u32,
}
struct Rect {
w: u32,
h: u32,
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side * self.side
}
}
impl Shape for Rect {
fn area(&self) -> u32 {
self.w * self.h
}
}
// every item has the same type, and the caller knows it: a generic (the bound is missing)
fn total_of<T: Shape>(items: &[T]) -> u32 {
items.iter().map(|item| item.area()).sum()
}
// items of different types, borrowed from the caller's stack, no Box: a trait object
fn total_mixed(items: &[&dyn Shape]) -> u32 {
items.iter().map(|item| item.area()).sum()
}
fn main() {
let squares = [Square { side: 1 }, Square { side: 2 }];
let rects = vec![Rect { w: 1, h: 2 }, Rect { w: 3, h: 4 }];
assert_eq!(total_of(&squares), 5);
assert_eq!(total_of(&rects), 14);
let nothing: [Square; 0] = [];
assert_eq!(total_of(&nothing), 0);
let sq = Square { side: 3 };
let rect = Rect { w: 4, h: 6 };
assert_eq!(total_mixed(&[&sq, &rect]), 33);
assert_eq!(total_mixed(&[&rect]), 24);
assert_eq!(total_mixed(&[]), 0);
// nothing was moved: the values are still ours
assert_eq!(sq.area() + rect.area(), 33);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more