Static + dynamic dispatch with one function

Hello,

Why do we need 2 functions for either static or dynamic dispatch ?

Let's say I have a function for static dispatch
fn <T: trait_method> (x: T) {
x.method();
}

I would like this method to work also for a trait object &trait_method, otherwise I have to define another function with the same code (but a different profile):
fn f (x: &trait_method) {
x.method();
}

Why can't rust be clever and call the static or dynamic version depending if he knows the type at compile time or not (for trait objects) ?

Thanks

dyn Trait already implements Trait in most cases, so the generic version should just work for trait objects too.

1 Like

This should work:

fn <T: trait_method +?Sized> (x: &T) {
x.method();
}
2 Likes

Looks really great !!!!!

Does it do exactly what I want ? I mean it does an optimised static dispatch with no overhead when possible (when type is known), right ?

I am a bit surprised because there is no mention in the doc. I think this is really interesting to write a single function to handle static + dynamic, but the doc shows them separately.

Thanks,

Yes; as @bjorn3 mentioned, dyn Trait objects implement Trait, so you just need to allow an unsized object to fill the type parameter. Each type that’s used will trigger its own code generation here, and the dyn Trait type will be the dynamic dispatch version.

Ok, thanks again both for your answers

In all cases the type is "known", and the dispatch is "static".

It just happens that for !Sized types such as dyn Trait, the statically dispatched method is one whose logic is to perform a dynamic dispatch on the type-erased value.

  • And similarly, dyn Trait is a "known" type, one that is encapsulating a type-erased value.

It may look like a formal / legal / nitpick distinction to say that it statically dispatches to a dynamic dispatch, rather than saying that it performs a dynamic dispatch directly, but this difference is what allows the generic function to Just Work: the generic function does not need to special case trait objects; it's the trait objects themselves that "special case themselves".

5 Likes

You usually don't -- monomorphization can let the caller pick dynamic dispatch in an implementation that's written like it uses static dispatch.

pub fn demo(x: impl Iterator<Item = i32>) -> i32 {
    x.sum()
}

fn main() {
    let it = 0..10;
    assert_eq!(demo(it), 45); // static dispatch
    
    let mut it = 0..10;
    let it: &mut dyn Iterator<Item = i32> = &mut it;
    assert_eq!(demo(it), 45); // dynamic dispatch
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8ed64ab7db6f0376f379553b3dc4353c

How does this work? Because there's an impl<I: Iterator + ?Sized> Iterator for &mut I, and dyn Iterator: Iterator.