Trait objects

In Rust, traits are types, but they are "unsized", which roughly means that they are only allowed to show up behind a pointer like std::boxed::Box⮳ (which points onto the heap) or & (which can point anywhere).

A type like &dyn ClickCallback or Box<dyn ClickCallback> where ClickCallback is a Trait, is called a "trait object", and includes a pointer to an instance of a type T implementing ClickCallback, and a 'vtable': a pointer to T's implementation of each method in the trait.

trait Draw {
    fn draw(&self);
}

struct Button;

impl Draw for Button {
    fn draw(&self) {
        println!("Button");
    }
}

struct Text;

impl Draw for Text {
    fn draw(&self) {
        println!("Text");
    }
}

struct Screen {
    components: Vec<Box<dyn Draw>>, // <-- trait object
}

impl Screen {
    fn new() -> Self {
        Screen {
            components: vec![Box::new(Button), Box::new(Text), Box::new(Text)],
        }
    }

    fn run(&self) {
        for component in self.components.iter() {
            // The purpose of trait objects is to permit "late binding" of
            // methods. Calling a method on a trait object results
            // in virtual dispatch at runtime. Here, `components` is
            // a mix of `Button` and `Text` structs.
            component.draw();
        }
    }
}

fn main() {
    let s = Screen::new();
    s.run();
}

The set of traits after dyn can be made up of an object-safe⮳ base trait plus any number of autotraits (one of std::marker::Send⮳, std::marker::Sync⮳, std::marker::Unpin⮳, std::panic::UnwindSafe⮳, and std::panic::RefUnwindSafe⮳ - see special traits⮳).

dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static

See also

Trait Objects (docs)