Handling return of enum with generic and non generic type in rust

Viewed 270

I am trying to create striped down redis clone in Rust. I got stuck trying to implement redis protocol. I want to have a struct that would encapsulate incoming packet and parse it appropriately to its data type and command.

My data types are simple Strings or Ints, but one thing is kinda different for Array data type i would like to use Vec<T> and this is where my problems begin.

So i created struct that represents my incoming packet. What to want to archive with this struct to be able to just do: let parsed_value = ProtocolPacket::new(data: &[u8])

struct ProtocolPacket <T> {  //  <-- this <T> is only because of ProtocolPacketData<T>
    command: ProtocolPacketCommand,
    data: ProtocolPacketData<T>,
}
impl <T>ProtocolPacket <T> {
    fn new(packet: &[u8]) -> ProtocolPacket<T> {
        let packet = packet.into_iter();
        let command = ProtocolPacketCommand::new(packet.next().unwrap()); // <-- This is working
    // Delete \r \n
    packet.next_back().unwrap();
    packet.next_back().unwrap();

    let data = packet.as_slice();
    let data = ProtocolPacketData::new(data);


    ProtocolPacket{
        command: command,
        data: data,
    }
}
enum ProtocolPacketData <T> {
    SimpleString(String), 
    Error(String),
    Integer(i64), 
    String(String), 
    Array(Vec<T>),
}

impl <T> ProtocolPacketData <T> {
    pub fn new(rest: &[u8]) -> ProtocolPacketData<T> {
        let rest = rest.into_iter();
        let data_type  = rest.next().unwrap();

        // Strip last 2 bytes -> \r\n
        rest.next_back().unwrap();
        rest.next_back().unwrap();

        match data_type.to_string().chars().next().unwrap() {
            '+' => handle_packet_simplestring(rest.as_slice()), <-- Function call that returns ProtocolPacketData<String>
            '-' =>   unimplemented!()
            ':' =>   unimplemented!()
            '$' =>   unimplemented!()
            '*' =>   unimplemented!()
            '%' =>   unimplemented!()
            _ =>    unimplemented!()
        }
    }
}


fn handle_packet_simplestring<T>(packet_value: &[u8]) -> ProtocolPacketData<String>{
    let data = String::from_utf8_lossy(packet_value);
    ProtocolPacketData::SimpleString(data.to_string())
}

This is where my error occours

    match data_type.to_string().chars().next().unwrap() {
        '+' => handle_packet_simplestring(rest.as_slice()), <-- Function call that returns ProtocolPacketData<String>
        '-' =>   unimplemented!()
        ':' =>   unimplemented!()
        '$' =>   unimplemented!()
        '*' =>   unimplemented!()
        '%' =>   unimplemented!()
        _ =>    unimplemented!()
    }

I get an error

mismatched types
expected enum ProtocolPacketData<T>
   found enum ProtocolPacketData<std::string::String>

How can i make this work and is this even viable way to do this? Thanks, Sam

1 Answers

The problem is that you are literally lying to the compiler. The impl<T> ProtocolPacketData<T> means that the "outside" of ProtocolPacketData<T> gets to decide whatever it wants T to be and that impl must apply. For example one should be able to do let x: ProtocolPacketData::<f64> = ProtocolPacketData::new(&buffer); but handle_packet_simplestring then puts a String where a f64 has to be. The compiler is trying to tell you that: It expects a ProtocolPacketData<T> - with no restriction on T - but you returned a specific ProtocolPacketData<String> (to which the other match-arms coerce because unimplemented!() returns !, so the compiler concludes it has to be String in any and all cases, regardless of T).

You probably want to remove the T-parameter entirely and extend ProtocolPacketData such as

enum ProtocolPacketData {
    // ...
    StringArray(Vec<Box<str>>),
    IntArray(Vec<u64>),
    // ...
}

and your big match returning a specific variant.

Another possibility is to

enum ProtocolPacketData {
    // ...
    Array(Vec<ProtocolPacketData>),
}

which makes the Array variant simpler; yet it allows for heterogeneous arrays ([Integer, String, Array[String, String]]) which may not be what you want.

Related