Search code examples
rusttraitsassociated-types

Hiding associated type shared by two traits


I have traits for senders and receivers of a specific message type.

pub trait Sends {
    type Message;
    fn send(&self) -> Self::Message;
}

pub trait Receives {
    type Message;
    fn receive(&mut self, msg: Self::Message);
}

I want to be able to store a compatible pair of sender and receiver in a struct with a run() method that passes messages, i.e. receiver.receive(sender.send()).

My intuition is that this run() method should not require knowledge of the message type (because all occurrences of the message type are handled internally), so the struct and its method should not expose the message type. I think keeping track of the message types also becomes impractical when you have a bigger sender-receiver network.

What is the best way to do this? I tried it out with Any, which mostly works. However,

  • I'm having difficulty actually creating a SendAny from a Send, and same for the receiver.
  • I hope there is a more elegant and efficient way, since this introduces boilerplate and needless boxing/unboxing.

Here is what I've got so far:

trait SendsAny {
    fn send_any(&self) -> Box<dyn Any>;
}

impl<T> SendsAny for T
where
    T: Sends,
    T::Message: 'static,
{
    fn send_any(&self) -> Box<dyn Any> {
        Box::new(self.send())
    }
}

// Similar for ReceivesAny

struct SendAndReceive {
    // These have to have matching Message types
    tx: Box<dyn SendsAny>,
    rx: Box<dyn ReceivesAny>,
}

impl SendAndReceive {
    fn new<M: 'static>(
        tx: Box<dyn Sends<Message = M>>,
        rx: Box<dyn Receives<Message = M>>,
    ) -> Self {
        // This doesn't work
        let tx = tx as Box<dyn SendsAny>;
        todo!()
    }

    fn run(&mut self) {
        self.rx.receive_any(self.tx.send_any());
    }
}

Solution

  • You should make the type that binds the Sends and Receives together, here SendAndReceiveInner. And then use a trait object, Box<dyn SendAndReceiveAny> to use it in the type-erased form in SendAndReceive.

    struct SendAndReceiveInner<R, S>
    where
        S: Sends,
        R: Receives<Message = S::Message>,
    {
        tx: S,
        rx: R,
    }
    
    trait SendAndReceiveAny {
        fn run(&mut self);
    }
    
    impl<R, S> SendAndReceiveAny for SendAndReceiveInner<R, S>
    where
        S: Sends,
        R: Receives<Message = S::Message>,
    {
        fn run(&mut self) {
            self.rx.receive(self.tx.send());
        }
    }
    
    struct SendAndReceive {
        inner: Box<dyn SendAndReceiveAny>,
    }
    
    impl SendAndReceive {
        fn new<R, S>(tx: S, rx: R) -> Self
        where
            S: Sends + 'static,
            R: Receives<Message = S::Message> + 'static,
        {
            Self {
                inner: Box::new(SendAndReceiveInner{ tx, rx }),
            }
        }
    
        fn run(&mut self) {
            self.inner.run();
        }
    }
    

    This has a lot less boxing involved but still a bit of boilerplate. You could just use it in the Box<dyn ...> form since the outermost SendAndReceive isn't doing much at this point, but encapsulation and API presentation is up to the reader.