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

Resources