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.
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:
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.
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!
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.