Conflicting implementations of trait

I have several different kinds of structs that I'm serializing and deserializing. I created a trait (DoesSomething below) to capture this behavior so I don't have to reimplement it for each struct, I can just add it with a one line impl DoesSomething for MyType {}

Some of these types include a CRC (CanDoSomethingElse), others don't. For the ones that do include the CRC, I want to check the CRC after deserializing, or calculate the CRC before serializing. I wanted to reimplement the methods of DoesSomething to include the CRC checking when implemented on a type that implements the CRC trait CanDoSomethingElse

However, I'm getting "conflicting implementations" -- what's the right way to reimplement the methods of DoesSomething depending on the other traits of the struct that DoesSomething is implemented on?

Here's a toy example


trait CanDoSomething {}
trait CanDoSomethingElse {}

pub trait DoesSomething {}
impl<T> DoesSomething for T where T: CanDoSomething {}
impl<T> DoesSomething for T where T: CanDoSomethingElse {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `DoesSomething`:
 --> src/lib.rs:9:1
  |
7 | impl<T> DoesSomething for T where T: CanDoSomething {}
  | --------------------------------------------------- first implementation here
8 | 
9 | impl<T> DoesSomething for T where T: CanDoSomethingElse {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

error: aborting due to previous error

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

You can't do this. Consider some alternate approach like having a single trait with a method that says which of the two behaviors it has.

2 Likes

Hi alice! Long time lurker here :wave:. Rust noob.

I'm really interested in knowing why this can't be done? If there is a good resource where I can read up on it?

It's because if some type implements both traits, then you are implementing DoesSomething for that type twice, which is not allowed. Additionally, it is not possible to use negative where bounds such as "type implements this trait, but not this other one" to avoid the overlap.

It's possible that the nightly-only unstable specialization feature can help, but I am not quite sure. I am not familiar with what unstable features can and can't do. I have a suspicion that it only helps in the case where it always implements one of them, but sometimes both.

2 Likes

One way to do this is to define an associated type that describes what validation strategy should be used:

struct Unchecked;
struct CheckCRC;

pub trait Validator<T:?Sized> {
    fn is_ok(_:&T)->bool { true }
}

impl<T> Validator<T> for Unchecked where T:CanDoSomething {}
impl<T> Validator<T> for CheckCRC where T:CanDoSomethingElse {}

trait CanDoSomething {}
trait CanDoSomethingElse {}

pub trait DoesSomething {
    type Validator: Validator<Self>;
    fn do_something(&self) {
        assert!(Self::Validator::is_ok(self));
    }
}

struct ExampleNoCRC;
impl CanDoSomething for ExampleNoCRC {}
impl DoesSomething for ExampleNoCRC { type Validator = Unchecked; }

struct ExampleWithCRC;
impl CanDoSomethingElse for ExampleWithCRC {}
impl DoesSomething for ExampleWithCRC { type Validator = CheckCRC; }

(Playground)

2 Likes

So because they could conflict, it's an error -- even though I don't have any types that overlap?

I must be confused. I thought this was a perfect use case for traits, to apply different behavior to objects that are composed differently.

Yes. This is unfortunate, but it's a casualty of Rust's efforts to ensure forward-compatibility in the face of uncoordinated changes-- It's part of what allows independently-developed crates the ability to make relatively large changes without risk of breaking their users' code.

In particular, rustc isn't worried so much about the traits your types implement, but instead about what traits are implemented by types in the larger ecosystem. Even types that aren't linked into the current project.

Traits add behaviors to structs, they can't restrict behaviors on them. You're trying to provide the same behavior twice, and in absense of a conflict resolution strategy, you're implicitly assuming that one trait prevents the other from being implemented. The compiler doesn't take a generic parameter like T and iterate over all of its implementors in every crate and check them each specifically; it reasons about them all as a group, so when you provide an impl for T through two different traits, there's no way for it to know there isn't a type in some crate somewhere that implements both of these traits.

With that said, if you're trying to use traits to provide default behaviors for objects, you just have to consider the possibility that both traits will be implemented and manually implement a strategy for deciding which takes priority. Even if Rust allowed you to do this, you'd still have to have some kind of strategy, as this is effectively a manifestation of the "diamond inheritance problem" in OOP languages.

3 Likes