Clarify static + dynamic dispatch in one function

Hello.
The question I have has partially been answered here

Here is an example I am playing with


pub trait X {
    fn x(&self) -> u64;
}

pub struct Y {}
impl X for Y {
    fn x(&self) -> u64 {
        return 1;
    }
}

pub struct Z {}
impl X for Z {
    fn x(&self) -> u64 {
        return 2;
    }
}

#[inline(never)]
pub fn generic_call_x<T: X + ?Sized>(x: &T) {
    println!("{}", x.x());
}

#[inline(never)]
pub fn dynamic_call_x(x: &dyn X) {
    println!("{}", x.x());
}

pub fn main() {
    let y = Y {};
    let z = Z {};

    generic_call_x(&y);
    generic_call_x(&z);

    dynamic_call_x(&y);
    dynamic_call_x(&z);

    let vec_of_x: Vec<Box<dyn X>> = vec![Box::new(Y {}), Box::new(Z {})];
    dynamic_call_x(vec_of_x[0].as_ref());
    dynamic_call_x(vec_of_x[1].as_ref());

    generic_call_x(vec_of_x[0].as_ref());
    generic_call_x(vec_of_x[1].as_ref());
}

I can achieve the same result with both dynamic_call_x and generic_call_x. godbolt explorer shows three instances of generic_call_x and one instance of dynamic_call_x. Most of the examples on dynamic dispatch in Rust use dyn Trait syntax. I can see it is more explicit, but are there other advantages in using dyn Trait syntax over generic variation, which also allows for static dispatch. What are scenarios when one if preferable over the other ?

Thanks.

I think the main reason to use &dyn Trait instead of generics is when you have a reason to avoid monomorphization -- you want shorter compile times and less generated code. That includes situations where you want to minimize binary size, e.g. some embedded scenarios. Conventional wisdom is that the code will be slower as it's harder to optimize through virtual calls, and you lose the ability for concrete-type-specific optimizations in each monomorphized version. (Depending on the situation though, you may end up with better instruction cache utilization if you have less code paths -- always measure.)

There are also plenty of situations where using dyn Trait instead of generics just isn't an option or is significantly less ergonomic, e.g.:

  • Not all traits can be a trait object
  • dyn Trait are dynamically sized types (DSTs), so if you want to own one, you need to put it behind a pointer redirection like Box<dyn Trait>
    • So if you take a Box<dyn X>, everyone has to box up before the call site.
    • But if you take a generic T: X, any T: X + Sized doesn't have to.
  • You can trait bound on multiple traits, but you can only dyn Trait on a single trait[0]
    • Though you can often work around this with a "new Trait" pattern
    • ([0]: You can add bounds for auto-traits like Send)
  • If a trait has an associated type, the concrete associated type must be part of the dyn Trait type, which is also less flexible than generics
1 Like

Thank you @quinedot.
I understood that generics is more flexible, and generally more performant, but in certain situations dynamic version may be preferable.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.