Using the examples from Dynamic Dispatch, we can start out with the following sample code:

enum Error { /* ...truncated... */ }
enum Status { /* ...truncated... */ }
struct Order { /* ...truncated... */ }
struct PrintObject { /* ...truncated... */ }
 
trait PrinterControl {
    fn print(pages: &PrintObject) -> Result<Order, Error>;
    fn cancel(order: &Order) -> Result<(), Error>;
    fn check_health() -> Result<Status, Error>;
}
 
struct Model1000 {}
impl PrinterControl for Model1000 { /* ...truncated... */ }
/* ...all the way to... */
struct Model5000 {}
impl PrinterControl for Model5000 { /* ...truncated... */ }
 
fn print_current_page(
    page_number: u32, selected_printer: &Box<dyn PrinterControl>
) -> Result<Order, Error> {
    selected_printer.print(document.by_number(page_number).into())
}

We have several different printer models, each a struct; they all then implement the PrinterControl trait to add functionality; and a method using dynamic dispatching to recieve printer models. As stated in Dynamic Dispatch, this has some significant performance implications, proved by:

Static Dispatch vs. Dynamic Dispatch Benchmarks

We can however use what is known as static dispatching to fix that.

One of the most common (and best!) ways to do static dispatching is to create a enum of all possible values, then implementing it with the methods. For example, to replace the above dynamic dispatch, we can do:

/* ...PrinterControl and dependencies truncated... */
/* ...models 1000 to 5000 truncated... */
 
enum PrinterModels {
  Model1000(Model1000),
  /* ...all the way up to... */
  Model5000(Model5000)
}
 
impl PrinterControl for PrinterModels {
    fn print(&self, pages: &PrintObject) -> Result<Order, Error> {
        match self {
            Self::Model1000(printer) => printer.print(pages),
            /* ...all the way up to... */
            Self::Model5000(printer) => printer.print(pages)
        }
    }
    /* repeat for cancel() and check_health() */
}
 
fn print_current_page(
    page_number: u32, selected_printer: PrinterModels
) -> Result<Order, Error> {
    selected_printer.print(document.by_number(page_number).into())
}

Now, we are able to accomplish the same thing, however without using the heap at all, which can greatly improve application performance. The main issue for this approach is that there is a lot of boilerplate code. Writing out the example for impl PrinterControl for PrinterModels above takes up ~35 lines. In a larger application with more methods and dispatches, that can lead to some ridicously large impl blocks which are hard to maintain.

Instead, we can use enum_dispatch. This crate allows us to simplify the above logic by removing the need for the impl block with a macro.

/* ...PrinterControl and dependencies
      and models 1000 to 5000 truncated... */
#[enum_dispatch]
trait PrinterControl { /* ...truncated... */ }
 
#[enum_dispatch(PrinterControl)]
enum PrinterModels {
  Model1000(Model1000),
  Model2000(Model2000),
  Model3000(Model3000),
  Model4000(Model4000),
  Model5000(Model5000),
}

The macro allows us to greatly simplify the boilerplate work, and it even supports having multiple traits or enums.