Rust's Static and Dynamic Dispatch
When invoking an object’s method in rust there are two dispatch mechanisims, static and dynamic.
pub trait Trait {
fn do_something(&self);
}
fn static_dispatch<T: Trait>(foo: &T) {
foo.do_something();
}
fn dynamic_dispatch(foo: &dyn Trait) {
foo.do_something();
}
Here we have a trait named Trait
. It expects implementations to provide a
do_something()
method. The do_something()
method takes &self
, a reference
to the concrete type which is implementing the Trait
. This means that
implementations of do_something()
will have full access to whatever type
implements the trait.
The static_dispatch()
function is a generic function that takes a reference to
a type that implements Trait
. This is resolved at compilation time, meaning
that wherever static_dispatch()
is called the compiler has to know what the
underlying type of foo
is.
The dynamic_dispatch()
function is not generic. It is a function that takes
a reference to an object that implements Trait
, a trait object. At run time
this will do a lookup on foo
to find the address of the do_something()
method and then call it.
Passing Trait Objects to Functions
We’ll define a simple type that implements Trait
and then try to use it with
the static_dispatch()
and the dynamic_dispatch()
functions:
pub struct Stuff {
pub value: u64,
}
impl Trait for Stuff {
fn do_something(&self) {
println!("Stuff: {}", self.value);
}
}
If we create a concrete instance of Stuff
we can pass it to either function
and it will compile and run fine:
fn main() {
let stuff = Stuff { value: 1 };
static_dispatch(&stuff);
dynamic_dispatch(&stuff);
}
We can create a Box
containing a Stuff
instance and pass it to either
function:
fn main() {
let stuff = Box::new(Stuff { value: 1 });
static_dispatch(&*stuff);
dynamic_dispatch(&*stuff);
}
We had to first dereference the contents of the Box
with the
dereference operator, *
, to get access to the
contained item, before we passed the reference.
It’s important to note that this a Box<Stuff>
. If we instead create a Box
of
the trait it will fail to compile.
fn main() {
let stuff: Box<dyn Trait> = Box::new(Stuff { value: 1 });
static_dispatch(&*stuff);
dynamic_dispatch(&*stuff);
}
We will be provided with an error similar to:
error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
--> src/main.rs:25:21
|
25 | static_dispatch(&*stuff);
| --------------- ^^^^^^^ doesn't have a size known at compile-time
| |
| required by a bound introduced by this call
|
= help: the trait `Sized` is not implemented for `dyn Trait`
note: required by a bound in `static_dispatch`
--> src/main.rs:5:20
|
5 | fn static_dispatch<T: Trait>(foo: &T) {
| ^ required by this bound in `static_dispatch`
help: consider relaxing the implicit `Sized` restriction
|
5 | fn static_dispatch<T: Trait + ?Sized>(foo: &T) {
| ++++++++
For more information about this error, try `rustc --explain E0277`.
Like many rust error messages, this one provides you with a potentional fix,
adding + ?Sized
to the trait bounds.
Implicitly Sized
Per the Sized
documentation. Sized
is an implicit trait on all type
parameters. It means that the size of the type is known at compile time. In
order to relax this bound one uses the ?Sized
syntax.
If we follow the error suggestion and modify static_dispatch()
to relax this
bounds, then the example will again compile and run.
fn static_dispatch<T: Trait + ?Sized>(foo: &T) {
foo.do_something();
}
I’ve got a feeling that + ?Sized
turns the static_dispatch()
function into a
dynamic dispatch implementation. I did some quick searching online and came up
empty