Where does Dynamic Dispatch stop?

Consider the following playground:

trait Fruit {
    fn eat(&self);
}

struct Apple {}
impl Fruit for Apple {
    fn eat(&self) {
        println!("ate an apple!");
    }
}

struct Pear {}
impl Fruit for Pear {
    fn eat(&self) {
        println!("ate a pear!");
    }
}

fn get_it(is_apple: bool) -> Box<dyn Fruit> {
    match is_apple {
        true => Box::new(Apple {}),
        false => Box::new(Pear {})
    }
}

fn eat_it<T: Fruit + ?Sized>(fruit:&T) {
    //Is this still using dynamic dispatch?
    fruit.eat();
}

fn main() {
    let apple = get_it(true);
    let pear = get_it(false);
    
    eat_it(&*apple);
    eat_it(&*pear);
}

As the comment asks - is eat_it() still using dynamic dispatch, or is it monomorphised ?

Assuming from there on it is static dispatch - is it possible to get rid of the ?Sized constraint but keep that performance?

The eat_it() fn itself is monomorphized, but the fruit.eat() line in its fn body uses dynamic dispatch because that's the only thing that can work after conversion to Box<dyn Fruit>.

fn eat_it<T: Fruit + ?Sized>(fruit:&T) {
    //Is this still using dynamic dispatch?
    fruit.eat();
}

What this code means is that the fruit parameter is of some type that we're gonna call T, that implements the Fuit trait (the ?Sized says that the size of T type may or may not be known at compile time).

Then let's consider the main fn. The apple and pear locals are both of type Box<dyn Fruit> (i.e. a trait object), and eat_it() is called on a borrowed deref on that, i.e. the arguments passed are of type &dyn Fruit i.e. a borrow to a trait object.
The only mechanism I know of that allows method calls on trait objects is dynamic dispatch using a vtable.

1 Like

Long story short: given that the inputs fed to eat_it in your main are of type &dyn Fruit, eat_it is monomorphised into eat_it::<dyn Fruit>, i.e., a function using dynamic dispatch:

/// eat_it<T = dyn Fruit>
fn eat_it_dyn_Fruit (fruit: &dyn Fruit)
{
    // fruit.eat()
    <dyn Fruit>::eat(fruit)
}
1 Like

Is there a way to coerce a Box<dyn T> into a specific struct that implements T, so that this cost can be avoided?

Dynamic dispatch only happens once per method call on a dyn Trait. All of the methods that they call are monomorphic.

trait Fruit {
    fn eat(&self) {
        println!("I'm gonna do it!");
        self.really_eat();
    }
    fn really_eat(&self);
}

let fruit: Box<dyn Fruit> = unimplemented!();
fruit.eat();

Here, eat uses dynamic dispatch, but it calls really_eat with static dispatch. So for instance, when you do write!(w, "a {} string", "format") on a dyn Write, it does dynamic dispatch once for write_fmt, but all of the calls to Write::write within it are monomorphised.

Hence, one possible solution amounts to providing larger methods on Fruit that do more things.

1 Like

Oh that's exactly what my concern was really about - i.e. needing to pass the Trait Object down all the way... good to know that's not really an issue, thanks!

FWIW Looking at this discussion: Convert Box<dyn T> to Box<dyn Any> - #20 by OptimisticPeach

Seems it is possible to get from a Box<dyn T> to a struct that implements T, if really necessary:

No because T is a type, not a trait, and types cannot be implemented for other types.
That's an ability unique to traits AFAIK.

Any casts involve dynamic dispatch too, but maybe that's still worthwhile if you can do this once before a bunch of method calls.

1 Like

@jjpe - not sure I'm following what you're saying exactly, and that example above does work...

Maybe you thought I was saying from Box<dyn T> to T as opposed to Box<dyn T> to a struct that implements T ?

T is often used as a generic type parameter, so it would represent a specific type. You're using it abstractly as the name of some trait, which is not wrong, but perhaps the source of confusion.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.