Skip to the content.

Trait Object VS Trait Bound

Trait bound

The impl Trait syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound; it looks like this:

pub trait Summary {
    fn summarize(&self) -> String;
}

// option 1: impl Trait syntax 
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// option 2: trait bound syntax
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

The impl Trait looks like to take trait as parameters but in fact it is trait bound – generic function with type constraint.

Trait as Parameter (Generics)

When you use a trait as a parameter with generics, the function is monomorphized at compile time, meaning it generates a specific version of the function for each type that you use.

In the following example, draw_shape is a generic function that can accept any type that implements the Draw trait. The compiler generates separate versions of draw_shape for Circle and Square.

trait Draw {
    fn draw(&self);
}

struct Circle;
struct Square;

impl Draw for Circle {
    fn draw(&self) {
        println!("Drawing a Circle");
    }
}

impl Draw for Square {
    fn draw(&self) {
        println!("Drawing a Square");
    }
}

fn draw_shape(shape: impl Draw) {
    shape.draw();
}

// the same thing with:
/*
fn draw_shape<T: Draw>(shape: T) {
    shape.draw();
} */

fn main() {
    let circle = Circle;
    let square = Square;

    draw_shape(circle);
    draw_shape(square);
}

Trait Object as Parameter

When you use a trait object as a parameter, the function can accept any type that implements the trait, but it uses dynamic dispatch at runtime.

In this example, draw_shape takes a reference to a trait object (&dyn Draw). This allows the function to accept any type that implements the Draw trait, but it uses dynamic dispatch to call the draw method at runtime.

trait Draw {
    fn draw(&self);
}

struct Circle;
struct Square;

impl Draw for Circle {
    fn draw(&self) {
        println!("Drawing a Circle");
    }
}

impl Draw for Square {
    fn draw(&self) {
        println!("Drawing a Square");
    }
}

fn draw_shape(shape: &dyn Draw) {
    shape.draw();
}

fn main() {
    let circle = Circle;
    let square = Square;

    draw_shape(&circle);
    draw_shape(&square);
}

Key Differences