1
votes

I am starting to play with Rust for a new library. I'm trying to wrap my head around the possible ways to implement the following.

What follows is more of desired expression not real syntax. All of the ways I've tried to express this either don't compile, or don't compile when I go to implement one of the alias traits.

struct ConcreteType;
struct CommonType;

trait Handler<Rin, Rout = Rin>{
    fn handle_event(&self, msg: &Rin);
}

// alias Handler with one of the types defined as a common case
trait HandlerToMessage<M> : Handler <ConcreteType, M>{
    fn handle_event(&self, msg: &ConcreteType) {
        // default implementation of parent trait
        // example is simplified, forget about how Rout/M is actually used
        self.decode(msg)
    }

    // method to implement
    fn decode(&self, msg: &ConcreteType) -> M;
}

// another alias for most common case where Rin/Rout are ConcreteType, CommonType most often
trait HandlerToCommonType : HandlerToMessage <ConcreteType, CommonType>{
    fn decode(&self, msg: &ConcreteType) -> CommonType 
    {
       ...
    };
}

Alternative using associated types

trait Handler{
    type Rin;
    type Rout;    // not yet able to do Rout = Rin with associated types

    fn handle_event(&self, msg: &Self::Rin) -> Self::Rout;
}


trait HandlerToMessage : Handler <Rin=ConcreteType>{
    fn handle_event(&self, msg: &Self::Rin) {
        // common functionality
        self.decode(msg)
    }

    // method to implement
    fn decode(&self, msg: &Self::Rin) -> Self::Rout;
}

trait HandlerToCommonType : HandlerToMessage <Rout=CommonType>{

    fn decode(&self, msg: &ConcreteType) -> CommonType
    {
        ...
    } 
}

In C++ this is roughly what I want to accomplish

// real world example I've seen in the wild of this structure
template <class Rout>
class Context {
public:
    void dispatch(Rout* msg);
};

template <class Rin, Rout = Rin>
class ReadHandler {
public:
    void read (Context* ctx, Rin* msg) = 0;

private:
    Context<Rout> ctx_;
};

// very common to convert from a byte buffer some message type
template <class M>
class BytesToMessageDecoder : ReadHandler<IOBuffer, M> {
public:
    // Template method pattern
    void read (Context* ctx, IOBuffer* msg) {
        M msgOut;
        bool success;
        success = this->decode(msg, &msgOut);
        if (success) {
            ctx->dispatch(msgOut);
        }                
    }

    bool decode(IOBuffer* msg, M* msgOut) = 0;

}

// convert one byte buffer to another is common
typedef BytesToMessageDecoder<IOBuffer> BytesToBytesDecoder;


// Concrete implementations
// look for fixed number of bytes incoming
class FixedLengthFrameDecoder : BytesToBytesDecoder {
    bool decode(IOBuffer* msg, IOBuffer* msgOut) { ... }
}

// fields are prefixed with a length. Wait for that many bytes and then dispatch
class LengthBasedFieldDecoder: BytesToBytesDecoder {
    bool decode(IOBuffer* msg, IOBuffer* msgOut) { ... }
}

class StringDecoder : BytesToMessageDecoder<std::string> { 
    // decode from byte buffer to a string
    bool decode(IOBuffer* msg, std::string* msgOut) { ... }
}

Basically the top level trait Handler is the most generic but maybe not meant to be implemented by anyone but advanced library users. The HandlerToMessage trait is meant to be a common conversion where we take ConcreteType and convert to some other type. The library may implement several of these. The HandlerToCommonType is the most common case that numerous library types would want to start from.

The details on how Rout is used in the Handler trait is not of importance. I tried to simplify the example and left off some arguments to hopefully make what I'm trying to convey more concise. All of my searching on this either has me thinking this isn't possible to convey or I am misusing it. I don't quite understand if this falls under the new specialization implementation, it doesn't feel like it from my understanding though.

I realize Rust is not C++ and so maybe what I'm trying to do is either not supported or has a different syntax. Any help is appreciated either in correct syntax or a more idiomatic Rust way.

1
Your first example doesn't use Rout at all. - Shepmaster
@Shepmaster I edited the C++ example to be more realistic I hope since I'm not positive I can convey the idea in Rust. Let me know if I can be more clear somehow. Thanks for sticking with me, much appreciated. - jeeves

1 Answers

0
votes

Perhaps you can just have separate traits and implement one for all implementers of the other:

struct ConcreteType;
struct CommonType;

trait Handler<Input, Output = Input> {
    fn handle_event(&self, msg: &Input) -> Output;
}

trait HandlerToMessage<M> {
    fn decode(&self, msg: &ConcreteType) -> M;
}

impl<T, M> Handler<ConcreteType, M> for T
    where T: HandlerToMessage<M>
{
    fn handle_event(&self, msg: &ConcreteType) -> M {
        self.decode(msg)
    }
}

impl HandlerToMessage<CommonType> for () {
    fn decode(&self, _msg: &ConcreteType) -> CommonType {
        unimplemented!()
    }
}

fn main() {}

The last one is really awkward because you'd normally implement a trait for a concrete type, but you haven't really presented any that make sense to implement for.