Home > Software design >  Are the different ways to combine traits in Rust equivalent?
Are the different ways to combine traits in Rust equivalent?

Time:12-01

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.

  • Related