Trait Object

  • A trait object is a fat pointer which is two-word values carrying the address of some value, along with some further information necessary to put the value to use.
  • A trait object is a reference to a value that implements a certain trait.
  • A trait object is an opaque value of another type that implements an object safe base trait, its auto traits(Send, Sync, Unpin, UnwindSafe, RefUnwindSafe), and any supertraits of the base trait.
  • Trait objects are written as the keyword dyn followed by a set of trait bounds - the first must be auto traits, and one life time if any. Paths to trait may be parenthesized.

e.g -

  • dyn Trait
  • dyn Trait + Send
  • dyn Trait + Send + Sync
  • dyn Trait + 'static
  • dyn Trait + Send + 'static
  • dyn Trait +
  • dyn 'static + Trait.
  • dyn (Trait)

Due to the opaqueness of which concrete type the value is of, trait objects are dynamically sized types. Like all DSTs, trait objects are used behind some type of pointer; for example &dyn SomeTrait or Box<dyn SomeTrait>. Each instance of a pointer to a trait object includes:

  • A pointer to an instance of a type T that implements SomeTrait
  • A virtual method table, often just called a vtable, which contains, for each method of SomeTrait and its supertraits that T implements, a pointer to T's implementation (i.e. a function pointer).

Object safe traits can be the base trait of a trait object Object Safety, Object safe trait

Ref: Rust Docs

Returning a single trait using impl

pub trait Runnable {
    fn run(&self);
}

struct Dog;
struct Cat;

impl Runnable for Dog {
    fn run(&self) {
        println!("The dog is running.")
    }
}

impl Runnable for Cat {
    fn run(&self) {
        println!("The cat is running.")
    }
}

fn get_running_dog() -> impl Runnable {
    Dog {}
}

Rust won't allow this

fn get_runner(kind: i32) -> impl Runnable {
    if kind == 1 {
        Dog {}
    } else {
        Cat {}
    }
}

Trait Object to rescue

fn get_runner_dyn(kind: i32) -> &'static dyn Runnable {
    if kind == 1 {
        &Dog {}
    } else {
        &Cat {}
    }
}

fn get_runner_box(kind: i32) -> Box<dyn Runnable> {
    if kind == 1 {
        Box::new(Dog {})
    } else {
        Box::new(Cat {})
    }
}

fn invoke_runner_dyn(runner: &dyn Runnable) {
    runner.run();
}

fn invoke_runner_box(runner: Box<dyn Runnable>) {
    runner.run();
}

full source code

pub trait Runnable {
    fn run(&self);
}

struct Dog;
struct Cat;

impl Runnable for Dog {
    fn run(&self) {
        println!("The dog is running.")
    }
}

impl Runnable for Cat {
    fn run(&self) {
        println!("The cat is running.")
    }
}

fn get_running_dog() -> impl Runnable {
    Dog {}
}

// but we can't do this in Rust
/*
fn get_runner(kind: i32) -> impl Runnable {
    if kind == 1 {
        Dog {}
    } else {
        Cat {}
    }
}
*/

fn get_runner_dyn(kind: i32) -> &'static dyn Runnable {
    if kind == 1 {
        &Dog {}
    } else {
        &Cat {}
    }
}

fn get_runner_box(kind: i32) -> Box<dyn Runnable> {
    if kind == 1 {
        Box::new(Dog {})
    } else {
        Box::new(Cat {})
    }
}

fn invoke_runner_dyn(runner: &dyn Runnable) {
    runner.run();
}

fn invoke_runner_box(runner: Box<dyn Runnable>) {
    runner.run();
}

fn main() {
    get_running_dog().run();
    get_runner_dyn(2).run();
    get_runner_box(1).run();

    invoke_runner_dyn(&Dog {});
    invoke_runner_box(Box::new(Dog {}));
}