impl Trait for Box<dyn Trait>
Let's look at how one implements Trait for Box<dyn Trait + '_>. One
thing to note off that bat is that most methods are going to involve
calling a method of the dyn Trait inside of our box, but if we just
use self.method() we would instantly recurse with the very method
we're writing (<Box<dyn Trait>>::method)!
We need to take care to call <dyn Trait>::method and not <Box<dyn Trait>>::method
in those cases to avoid infinite recursion.
Now that we've highlighted that consideration, let's dive right in:
#![allow(unused)] fn main() { trait Trait { fn look(&self); fn boop(&mut self); fn bye(self) where Self: Sized; } impl Trait for Box<dyn Trait + '_> { fn look(&self) { // We do NOT want to do this! // self.look() // That would recursively call *this* function! // We need to call `<dyn Trait as Trait>::look`. // Any of the below forms work, it depends on // how explicit you want to be. // Very explicit // <dyn Trait as Trait>::look(&**self) // Yay auto-deref for function parameters // <dyn Trait>::look(self) // Very succinct and a "makes sense once you've // seen it enough times" form. The first deref // is for the reference (`&Self`) and the second // deref is for the `Box<_>`. (**self).look() } fn boop(&mut self) { // This is similar to the `&self` case (**self).boop() } fn bye(self) { // Uh... see below } } }
Oh yeah, that last one. Remember what we said before?
dyn Trait doesn't have this method, but Box<dyn Trait + '_> does.
The compiler isn't going to just guess what to do here (and couldn't if,
say, we needed a return value). We can't move the dyn Trait out of
the Box because it's unsized. And we can't
downcast from dyn Trait
either; even if we could, it would rarely help here, as we'd have to both
impose a 'static constraint and also know every type that implements our
trait to attempt downcasting on each one (or have some other clever scheme
for more efficient downcasting).
Ugh, no wonder Box<dyn Trait> doesn't implement Trait automatically.
Assuming we want to call Trait::bye on the erased type, are we out of luck?
No, there are ways to work around this:
#![allow(unused)] fn main() { // Supertrait bound trait Trait: BoxedBye { fn bye(self); } trait BoxedBye { // Unlike `self: Self`, this does *not* imply `Self: Sized` and // thus *will* be available for `dyn BoxedBye + '_`... and for // `dyn Trait + '_` too, automatically. fn boxed_bye(self: Box<Self>); } // We implement it for all `Sized` implementors of `trait: Trait` by // unboxing and calling `Trait::bye` impl<T: Trait> BoxedBye for T { fn boxed_bye(self: Box<Self>) { <Self as Trait>::bye(*self) } } impl Trait for Box<dyn Trait + '_> { fn bye(self) { // This time we pass `self` not `*self` <dyn Trait as BoxedBye>::boxed_bye(self); } } }
By adding the supertrait bound, the compiler will supply an implementation of
BoxedBye for dyn Trait + '_. That implementation will call the implementation
of BoxedBye for Box<Erased>, where Erased is the erased base type. That
is our blanket implementation, which unboxes Erased and calls Erased's
Trait::bye.
The signature of <dyn Trait as BoxedBye>::boxed_bye has a receiver with the
type Box<dyn Trait + '_>, which is exactly the same signature as
<Box<dyn Trait + '_> as Trait>::bye. And that's how we were able to
complete the implementation of Trait for Box<dyn Trait + '_>.
Here's how things flow when calling Trait::bye on Box<dyn Trait + '_>:
<Box<dyn Trait>>::bye (_: Box<dyn Trait>) -- just passed -->
< dyn Trait >::boxed_bye(_: Box<dyn Trait>) -- via vtable -->
< Erased >::boxed_bye(_: Box< Erased >) -- via unbox -->
< Erased >::bye (_: Erased ) :)
There's rarely a reason to implement BoxedBye for Box<dyn Trait + '_>, since
that takes a nested Box<Box<dyn Trait + '_>> receiver.
Any Sized implementor of Trait will get our blanket implementation of
the BoxedBye supertrait "for free", so they don't have to do anything
special.
The last thing I'll point out is how we did
#![allow(unused)] fn main() { trait Trait {} impl Trait for Box<dyn Trait + '_> { // this: ^^^^ } }
We didn't need to require 'static, so this is more flexible.
It's also very easy to forget.