🎓 A1Learner
🔍

Rust Trait Bounds

A trait bound is a requirement that a generic type must be able to do something. The right choice between fixing a type at compile time and deciding it at run time depends on whether all values share one type or different types must mix.

Combine bounds with +

T: Display + Clone means T must do both. Here the bound lets one function clone a value and print it:

fn twice<T: Display + Clone>(x: T) {
    println!("{} {}", x.clone(), x);
}
twice(String::from("go"));

impl Trait: fixed at compile time

impl Trait as an input type means: some type that implements the trait. The compiler fixes the real type while building:

fn label(x: impl Display) -> String { format!("[{}]", x) }
println!("{}", label(42));

dyn Trait: chosen at run time

Box<dyn Trait> is a trait object: any implementing type, chosen at run time. Trait objects let one list hold different types together:

let crew: Vec<Box<dyn Speak>> = vec![Box::new(Cat), Box::new(Robot)];
for c in &crew { println!("{}", c.speak()); }

Two impl parameters may differ

Two &impl Trait parameters may be different types. Two parameters sharing one T would have to be the same concrete type:

fn show_two(a: impl Display, b: impl Display) { println!("{} | {}", a, b); }
show_two(1, "one");

A bound can refuse, too

A bound also refuses, not just allows: Vec has no Display, so this line is refused with error E0277. An array fails a T: Debug + Display bound the same way — it has Debug, but no Display.

struct Wrapper<T: Display>(T);
let w = Wrapper(vec![1, 2, 3]);

The turbofish needs a named letter

A generic fn may be called as show::<i32>(...), because T has a name. fn show(x: impl Display) has no letter to name. So calling it with the turbofish fails. It fails with error E0107. Call it plainly instead. Let Rust infer:

fn show<T: Display>(x: T) { println!("{}", x); }
show::<i32>(42);

Example

trait Speak { fn speak(&self) -> &str; }
struct Cat; struct Robot;
impl Speak for Cat { fn speak(&self) -> &str { "meow" } }
impl Speak for Robot { fn speak(&self) -> &str { "beep boop" } }
fn main() {
    let crew: Vec<Box<dyn Speak>> = vec![Box::new(Cat), Box::new(Robot)];
    for c in &crew { print!("{} ", c.speak()); }
    println!();
}

Key points

  • Use a where clause to keep long bounds readable
  • A trait with generic methods, or methods returning Self by value, cannot be a trait object
  • Trait objects trade a little speed for flexibility.
  • You pay for a lookup.
  • The lookup goes through a table of methods.
  • That table is called a vtable.
  • You pay only when you call.
  • A bound states what a generic type must be able to do.

In this example, Cat and Robot are different types that both implement Speak. main puts them in one Vec — and a Vec normally holds only one type. Box<dyn Speak> makes it possible. Each box holds "some type that can speak". Rust keeps a small method table. So speak() finds the right code for each. The loop prints each speak() result in order: "meow" from the cat, then "beep boop" from the robot. The choice happened at run time, which is exactly what dyn is for.

Note

Generics are usually faster, because the compiler knows the exact type.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

When should you prefer dyn Trait over a generic?