There are two ways to "combine traits" in Rust, I believe. I'm wondering what the differences are.
To be concrete, let's say the two traits are Beeps
and Honks
trait Beeps { fn beep(&self); }
trait Honks { fn honk(&self); }
A new empty trait can be formed, as explained here:
impl<T: Beeps Honks> BeepsAndHonks for T {}
For concreteness, an implementation would just be this:
struct Obj {}
impl Beeps for Obj { fn beep(&self) {} }
impl Honks for Obj { fn honk(&self) {} }
The combined trait can be used like so
fn factory() -> Box<dyn BeepsAndHonks> {
Box::new( Obj {} )
}
The other way to achieve a "combined trait" is via suptertraits:
trait BeepsAndHonks2: Beeps { fn honk(&self); }
This uses Beeps
as a base and layers a honk
on top. (Sorry for all the silly beeping and honking.)
When I don't own Beeps
and Honks
then the new empty trait is the only way to do this (I believe). Otherwise I can do either new empty trait or supertrait. Structurally the two approaches are different, but under the hood there is no difference, e.g. regarding number of vtable lookups, is that correct?
Since traits clearly can be combined, what speaks against allowing the Beeps Honks
syntax, e.g. fn factory() -> Box<dyn Beeps Honks>
?
CodePudding user response:
These two ways are not equivalent. Let's see what different options actually mean:
Generic implementation of supertrait
Your first example:
trait Beeps { fn beep(&self); }
trait Honks { fn honk(&self); }
trait BeepsAndHonks {}
impl<T: Beeps Honks> BeepsAndHonks for T {}
struct Obj {}
impl Beeps for Obj { fn beep(&self) {} }
impl Honks for Obj { fn honk(&self) {} }
Here you provide implementation of the BeepsAndHonks
trait for any trait that have both Beeps
and Honks
type. This is convenient not have to write implementation for every such type, but note that you can have a type implementing BeepsAndHonks
that cannot neither beep nor honk:
struct AnotherObj {}
impl BeepsAndHonks for AnotherObj {}
Supertraiting Honks
Your second example:
trait BeepsAndHonks2: Beeps { fn honk(&self); }
is different from the first. In this case you can have a type a that implements Beeps
or both Beeps
and Honks
together, but not only Honks
as allowed by the first example.
Supertrait with bounds
You can prevent the problem in the first example by requiring BeepsAndHonks
to also implement related traits:
trait BeepsAndHonks: Beeps Honks {}
Now AnotherObj
cannot exist. If you add Beeps
and Honks
implementations for it, it will have two conflicting implementaions of BeepsAndHonks
(one manual and one generic from the first example). And if your don't provide them trait bounds are not satisfied.