“This is the 19th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021”


Example: PacketType

Let’s take a different example. Suppose we are now implementing a library for a network protocol where the first byte of the data packet header tells us the type of packet. A reasonable solution is to represent packet types with an enumeration, where each variable maps to a packet type. Such as:

/// Represents a packet type.
/// Associated with each variant is its raw numeric representation.
enum PacketType {
    Data  = 0.// packet carries a data payload
    Fin   = 1.// signals the end of a connection
    State = 2.// signals acknowledgment of a packet
    Reset = 3.// forcibly terminates a connection
    Syn   = 4.// initiates a new connection with a peer
}
Copy the code

Given this notation, how should we convert to and from bytes?

The traditional approach, very common in C and C++ programs, is to simply convert a value from one type to another. This can also be done in Rust; For example, converting PacketType::Data to bytes is as simple as converting PacketType::Data to U8. This seems to have solved the problem of encoding PacketType as a byte representation, but we’re not done yet.

Did you notice that each PacketType variable has an associated value? They define the representation of the variation in the generated code. If we follow the usual Rust style of not assigning any value to the variables, then the numeric representation of each variant will depend on the order in which they are declared, which would result in an error if we simply converted the enumerated variant to a numeric type. A better way to convert enumeration variants to correct values is explicit matching.

impl From<PacketType> for u8 {
    fn from(original: PacketType) -> u8 {
        match original {
            PacketType::Data  => 0,
            PacketType::Fin   => 1,
            PacketType::State => 2,
            PacketType::Reset => 3,
            PacketType::Syn   => 4,}}}Copy the code

Pretty straightforward, right! Since the mapping From PacketType to U8 is included in the implementation of From, we can remove the value assigned to the PacketType variant, resulting in a more concise enumeration definition.

Reverse conversion?

According to the FAQ, converting enumerations to integers can be done by cast, as we have seen. However, the opposite transformation can (and, I think, in many cases, should) be implemented with matching statements. For ease of use and better engineering, it’s usually a good idea to implement From

for transitions between two (symmetric) directions.

Converting a PacketType to U8 is generally safe and correct, except for the caveats we saw earlier, because for each PacketType variable, there is a corresponding representation compatible with U8. However, the reverse is not true: converting u8 values without the corresponding PacketType variable is undefined behavior.

Although we can map any PacketType variable to a U8 value, we can’t map any U8 to PacketType in reverse: too much U8 and not enough PacketType! So for u8 to PacketType conversions, we can’t simply match u8 values and return the corresponding PacketType variable.

Therefore, for the U8 to PacketType conversion, we cannot simply match the U8 value and return the appropriate PacketType variant, as we did for the reverse conversion. We need a way to prompt that the conversion failed, but call Panic! () is not an acceptable option. We need a fallible From.