Combining traits
Rust has no support for directly combining multiple non-auto traits
into one dyn Trait1 + Trait2
:
#![allow(unused)] fn main() { trait Foo { fn foo(&self) {} } trait Bar { fn bar(&self) {} } // Fails let _: Box<dyn Foo + Bar> = todo!(); }
However, the methods of a supertrait are available to the subtrait.
What's a supertrait? A supertrait is a trait bound on Self
in the
definition of the subtrait, like so:
#![allow(unused)] fn main() { trait Foo { fn foo(&self) {} } trait Bar { fn bar(&self) {} } trait Subtrait: Foo // ^^^^^^^^^^^^^ A supertrait bound where Self: Bar, // ^^^^^^^^^ Another one {} }
The supertrait bound is implied everywhere the subtrait bound is present, and the methods of the supertrait are always available on implementors of the subtrait.
Using these relationships, you can support something analogous to
dyn Foo + Bar
by using dyn Subtrait
.
trait Foo { fn foo(&self) {} } trait Bar { fn bar(&self) {} } impl Foo for () {} impl Bar for () {} trait Subtrait: Foo + Bar {} // Blanket implement for everything that meets the bounds... // ...including non-`Sized` types impl<T: ?Sized> Subtrait for T where T: Foo + Bar {} fn main() { let quz: &dyn Subtrait = &(); quz.foo(); quz.bar(); }
Note that despite the terminology, there is no sub/super type relationship
between sub/super traits, between dyn SubTrait
and dyn SuperTrait
,
between implementors of said traits, et cetera.
Traits are not about sub/super typing.
Manual supertrait upcasting
Supertrait upcasting is planned, but not yet stable.
Until stabilized, if you need to cast something like dyn Subtrait
to dyn Foo
, you
have to supply the implementation yourself.
For a start, we could build it into our traits like so:
#![allow(unused)] fn main() { trait Foo { fn foo(&self) {} fn as_dyn_foo(&self) -> &dyn Foo; } }
But we can't supply a default function body, as Self: Sized
is required to perform
the type erasing cast to dyn Foo
. We don't want that restriction or the method
won't be available on dyn Supertrait
, which is not Sized
.
Instead we can separate out the method and supply an implementation for all Sized
types, via another supertrait:
#![allow(unused)] fn main() { trait AsDynFoo { fn as_dyn_foo(&self) -> &dyn Foo; } trait Foo: AsDynFoo { fn foo(&self) {} } }
And then supply the implementation for all Sized + Foo
types:
#![allow(unused)] fn main() { trait AsDynFoo { fn as_dyn_foo(&self) -> &dyn Foo; } trait Foo: AsDynFoo { fn foo(&self) {} } impl<T: /* Sized + */ Foo> AsDynFoo for T { fn as_dyn_foo(&self) -> &dyn Foo { self } } }
The compiler will supply the implementation for both dyn AsDynFoo
and dyn Foo
.
When we put this altogether with the Subtrait
from above, we can now utilize
an explicit version of supertrait upcasting:
trait Foo: AsDynFoo { fn foo(&self) {} } trait Bar: AsDynBar { fn bar(&self) {} } impl Foo for () {} impl Bar for () {} trait AsDynFoo { fn as_dyn_foo(&self) -> &dyn Foo; } trait AsDynBar { fn as_dyn_bar(&self) -> &dyn Bar; } impl<T: Foo> AsDynFoo for T { fn as_dyn_foo(&self) -> &dyn Foo { self } } impl<T: Bar> AsDynBar for T { fn as_dyn_bar(&self) -> &dyn Bar { self } } trait Subtrait: Foo + Bar {} impl<T: ?Sized> Subtrait for T where T: Foo + Bar {} fn main() { let quz: &dyn Subtrait = &(); quz.foo(); quz.bar(); let _: &dyn Foo = quz.as_dyn_foo(); let _: &dyn Bar = quz.as_dyn_bar(); }