🚚 Dynamic Dispatch

In computer science, dynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at run time.

Wikipedia.

Generics are Rust's approach to provide static dispatch. it is called static because at compile time disjoint code is generated prior to compilation and as such it is static in nature, and in fact no dispatch at all is happening at runtime.

There are however scenarios where dynamic disaptch shines:

  • allowing to inject / select logic based on external runtime input;
  • managing collections of different kind of data structures (e.g. different kinds of end point services in a single router).

In Rust dynamic dispatch is supported through trait objects, using the dyn keyword. Traditionally that's combined with a Box (e.g. Box<dyn Service>), but some prefer to use Arc instead of Box for reasons not mentioned here. One can also support it through the enum sum type, and there is even a crate named enum_dispatch, to help you automate this process. The latter is faster then true dynamic dispatch.

🤷 Either

#![allow(unused)]
fn main() {
pub enum Either<A, B> {
    A(A),
    B(B),
}
}

Rama provides the rama::service::util::combinator::Either combinator, for two variants, up to nine (Either9). These have as the sole purpose to provide you with an easy way to dynamically dispatch a Layers, Services, retry::Policy and a limit::Policy.

You can also implement it for your own traits in case you have the need for it.

In /examples/http_rate_limit.rs you can see it in action.

😵‍💫 Async Dynamic Dispatch

Only object safe traits can be the base trait of a trait object. You can learn more about this at https://doc.rust-lang.org/reference/items/traits.html#object-safety. At the moment traits with impl Trait return values are not yet object safe. Luckily however there is a workaround, so even though we do not encourage it, if desired you can box Services.

The approach taken to allow for this was widely published in the rust blog at https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html, which originally was mentioned at https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/builder-provider-api.html#dynamic-dispatch-behind-the-api.

The result is that rama has a BoxService, which can easily be created using the Service::boxed method, which is implemented automatically for all Services.