Implementing Mutually Exclusive Traits and Trait Bounds

I know there is no such thing as mutually exclusive traits, but I am following the suggestion of Lukas Kalbertodt and using associated types to help me. Please bear with me since my MWE requires a bit of boilerplate.

Assume I have Message and Handler traits, e.g. like in a (very) simplified version of actix. Handlers can handle messages and react accordingly. However, unlike in actix, Messages can also be assigned a priority via an associated constant like so:

use std::fmt::Debug;

trait MessageMarker {}
struct Urgent;
struct Normal;

impl MessageMarker for Urgent {}
impl MessageMarker for Normal {}

trait Message : Debug {
    type Prio : MessageMarker;
}

#[derive(Debug)]
struct SayHello;

#[derive(Debug)]
struct ActivateLifeSupport;

impl Message for SayHello {
    type Prio = Normal;
}

impl Message for ActivateLifeSupport {
    type Prio = Urgent;
}

trait Handler<M> where M : Message {
    fn handle(&self, msg : M);
}

struct MedicalDevice;

Now if I want to make my MedicalDevice a handler for any Message, I can do that

impl<M> Handler<M> for MedicalDevice
where M : Message {
    fn handle(&self, msg : M) {
        println!("Received {:?}",msg);
    }
}

This works completely fine. The problem comes when I want to make the device react differently to messages according to their priority:

impl<M> Handler<M> for MedicalDevice
where M : Message<Prio=Normal> {
    fn handle(&self, msg : M) {
        println!("Reacting slowly to {:?}",msg);
    }
}

impl<M> Handler<M> for MedicalDevice
where M : Message<Prio=Urgent> {
    fn handle(&self, msg : M) {
        println!("Reacting urgently to {:?}",msg);
    }
}

Now this produces a compiler error like so

error[E0119]: conflicting implementations of trait `Handler<_>` for type `MedicalDevice`
  --> src/main.rs:53:1
   |
46 | / impl<M> Handler<M> for MedicalDevice
47 | | where M : Message<Prio=Normal> {
48 | |     fn handle(&self, msg : M) {
49 | |         println!("Received {:?}",msg);
50 | |     }
51 | | }
   | |_- first implementation here
52 | 
53 | / impl<M> Handler<M> for MedicalDevice
54 | | where M : Message<Prio=Urgent> {
55 | |     fn handle(&self, msg : M) {
56 | |         println!("Urgently {:?}",msg);
57 | |     }
58 | | }
   | |_^ conflicting implementation for `MedicalDevice`

To my understanding no type M could implement both Message<Prio=Normal> and Message<Prio=Urgent>, so why is the compiler upset? What am I missing here?

Here is a link to the MWE in the Playground.

Thanks for your help.

1 Like

This is a long standing bug in the compiler

You can work around it with

trait Handler<M, P=M::Prio> where M : Message {
    fn handle(&self, msg : M);
}

I'm on mobile right now, so I can't check if you need to specify the second parameter in impl blocks.

1 Like

Oh neat (kind of). Can you point me to the gh issue once you have time?

Also, is there any workaround in case I cannot modify the handler trait itself? In my case the handler trait is provided by a different crate, but I can modify the Message trait.

I believe it's this issue which is blocked on needing an RFC (and probably Chalk) at this point.

2 Likes

In that case you can create a local copy of Handler and then write the blanket impl in terms of the local copy. The local copy will contain the actual implementation.

use some_crate::Handler;

trait MyHandler<M, P = M::Prio> where M : Message {
    fn my_handle(&self, msg : M);
}

impl<M> MyHandler<M, Normal> for MedicalDevice
where M : Message<Prio=Normal> {
    fn my_handle(&self, msg : M) {
        println!("Reacting slowly to {:?}",msg);
    }
}

impl<M> MyHandler<M, Urgent> for MedicalDevice
where M : Message<Prio=Urgent> {
    fn my_handle(&self, msg : M) {
        println!("Reacting urgently to {:?}",msg);
    }
}

impl<M> Handler<M> for MedicalDevice
where
    M: Message,
    Self: MyHandler<M>,
{
    fn handle(&self, msg : M) {
        self.my_handle(msg)
    }
}

looks like you do need to specify the second type parameter explicitly :frowning_face:

3 Likes

Oh that is nifty, I'll give it a try tomorrow.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.