简体   繁体   中英

Implementing trait methods for specific types

I'm trying to create a Coder trait with two methods (encode and decode). Each implementation of the trait needs to deal only with a specific type of input/output, so for instance:

  • BytesCoder should only receive &[u8] as inputs to encode and should only generate &[u8] as decoded outputs.
  • KVCoder should only receive HashMaps as inputs to encode and should only generate HashMaps as decoded outputs.

Approach 1: a simple way to satisfy the compiler would be to make encode and decode accept Any, and have each implementation deal with it separately. This seems like a bad idea since each coder should only deal with a single type.

type SomeEncodableHopefully = Box<dyn std::any::Any>;

pub trait Coder {
    fn encode(&self, element: SomeEncodableHopefully) -> &[u8];

    fn decode(&self, bytes: &[u8]) -> SomeEncodableHopefully;
}

pub struct BytesCoder {
    ...
}

impl BytesCoder {
    pub fn new() -> Self {
        ...
    }
}

impl Coder for BytesCoder {]
    // "element" should only be &[u8] instead of Any
    fn encode(&self, element: SomeEncodableHopefully) -> &[u8] {
        ...
    }

    // The output shouldn't be Any either
    fn decode(&self, bytes: &[u8]) -> SomeEncodableHopefully {
        ...
    }
}

pub struct KVCoder {
    ...
}

impl KVCoder {
    pub fn new() -> Self {
        ...
    }
}

impl Coder for KVCoder {]
    // "element" should only be HashMap<...> instead of Any
    fn encode(&self, element: SomeEncodableHopefully) -> &[u8] {
        ...
    }

    // The output shouldn't be Any either
    fn decode(&self, bytes: &[u8]) -> SomeEncodableHopefully {
        ...
    }
}

fn test_coder(coder: Box<dyn Coder>, to_encode: SomeEncodableHopefully, to_decode: &[u8]) {
    let encoded:&[u8] = coder.encode(to_encode);
    let decoded:SomeEncodableHopefully = coder.decode(to_decode);
}

Approach 2: I tried to use an associated type, but I wasn't able to succesfully define a fixed type for each implementation of Coder:

pub trait Coder {
    // The compiler ignores Element on each implementation and requires
    // this to be defined manually when a coder is used
    type Element;

    fn encode(&self, element: Self::Element) -> &[u8];

    fn decode(&self, bytes: &[u8]) -> Self::Element;
}

...

impl Coder for BytesCoder {
    // This gets ignored
    type Element = &[u8];

    fn encode(&self, element: Self::Element) -> &[u8] {
        ...
    }

    fn decode(&self, bytes: &[u8]) -> Self::Element {
        ...
    }
}

...

// Error on coder: the value of the associated type `Element` (from trait `Coder`) must be specified
fn test_coder(coder: Box<dyn Coder>, to_encode: SomeEncodableHopefully, to_decode: &[u8]) {
    let encoded:&[u8] = coder.encode(to_encode);
    let decoded:SomeEncodableHopefully = coder.decode(to_decode);
}

Approach 3: it could be a good idea to define type-specific versions of encode and decode. I had no complaints from the compiler, but this would be pretty verbose if there are many types involved (including, for instance: HashMap<A,B>, HashMap<A,C>, ...):

pub trait Coder {
    fn encode_to_bytes(&self, element: &[u8]) -> &[u8] {
        panic!("Invalid operation for this type of coder");
    }

    fn decode_to_bytes(&self, bytes: &[u8]) -> &[u8] {
        panic!("Invalid operation for this type of coder");
    }

    fn encode_to_some_other_type (&self, element: SomeOtherType) -> &[u8] {
        panic!("Invalid operation for this type of coder");        
    }

    fn decode_to_some_other_type (&self, bytes: &[u8]) -> SomeOtherType {
        panic!("Invalid operation for this type of coder");        
    }

}

...

// Ok
impl Coder for BytesCoder {
    fn encode_to_bytes(&self, element: &[u8]) -> &[u8] {
        ...
    }

    fn decode_to_bytes(&self, bytes: &[u8]) -> &[u8] {
        ...
    }
}

...

The third approach seems to solve the problem somewhat decently, but is there a better way to achieve this?

The error of your second approach requests you to specify the type of Coder it has to match with the type of to_encode for this to work. I introduce a generic here to make it as flexible as possible.

fn test_coder<E>(coder: Box<dyn Coder<Element = E>>, to_encode: E, to_decode: &[u8]) {
    let encoded: &[u8] = coder.encode(to_encode);
    let decoded: E = coder.decode(to_decode);
}

Which leaves open the question of who owns your slices that SirDarius raised. You probably should return an owned type instead since generally you can't take a &[u8] from every type:

pub trait Coder {
    type Element;

    fn encode(&self, element: Self::Element) -> Vec<u8>;
    fn decode(&self, bytes: &[u8]) -> Self::Element;
}

struct BytesCoder;
type SomeEncodableHopefully = Vec<u8>;
impl Coder for BytesCoder {
    type Element = Vec<u8>;

    fn encode(&self, element: Self::Element) -> Vec<u8> {
        element
    }

    fn decode(&self, bytes: &[u8]) -> Self::Element {
        bytes.to_vec()
    }
}

If Element has to be a reference you can use a GAT in it's stead. Or make the trait take a lifetime.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM