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.