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 {})); }