How to create helper traits?

This code does not compile:

trait Blarg {
    fn blarg(&self, a: i32, b: i32) -> i32;
}

trait BlargA {
    fn blarg_a(&self, a: i32) -> i32;
}
impl<T: BlargA> Blarg for T {
    fn blarg(&self, a: i32, _b: i32) -> i32 {
        self.blarg_a(a)
    }
}

trait BlargB {
    fn blarg_b(&self, b: i32) -> i32;
}
impl<T: BlargB> Blarg for T {
    fn blarg(&self, _a: i32, b: i32) -> i32 {
        self.blarg_b(b)
    }
}

Is there any easy way to do this or something like this? There is a lot more code in this case than just ignoring a parameter btw.

The main question is, what should your code do if some type implements both BlargA and BlargB?

Compile error, same error as when you attempt to implement a trait twice (since that's essentially what you're doing).

In that case, implementing BlargA or BlargB for any type can be a breaking change. One of the Rust's design decisions is to avoid this.

You're running into something called Coherence. That is, the compiler needs a way to always determine whether a type implements a particular trait and if so, which impl block should it use.

As has already been mentioned, the problem with allowing both Blarg impls is that if some type Foo implements both BlargA and BlargB it's not possible to decide which impl to use for <T as Blarg>::blarg().

Your proposition of just making it a compile error to implement BlargA and BlargB at the same time, while seemingly fine at first glance, has a couple problems... The most severe being that it moves the compilation error away from the code which is at fault (the overlapping impl<T: BlargXXX> Blarg for T blocks) and over to where it is used.

This is how C++ templates work and leads to hard-to-debug compile errors and non-local reasoning.

Depending on your situation, you've got a variety of tools at your disposal.

  1. Specialisation - if the compiler can see that one implementation is strictly more specific than another, the decision is no longer ambiguous
  2. A newtype wrapper which implements Blarg via BlargA, and another wrapper which does the same for BlargB
  3. Use helper functions instead of a helper trait
  4. Add another layer of indirection (see things like the futures::future::IntoFuture and IntoIterator traits)
  5. Rephrasing Blarg (or the specialised BlargA and BlargB) so you don't get into this situation (probably not a helpful response, but worth mentioning anyway)
3 Likes