Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 23 of 26

Medium Primitives

Decode bytes into enum variants

Repair Lvl::from_u8 so it recognizes the protocol's five log levels and rejects every other byte. Keep the enum and public function signature:

pub fn decode_levels(codes: Vec<u8>) -> Vec<String>
Byte Output
10 "Trace=10"
20 "Debug=20"
30 "Info=30"
31 "Warn=31"
50 "Error=50"
Any other value from 0 through 255 "?={code}", using decimal digits without padding

Preserve input order and duplicates. Empty input returns an empty vector. For example, [10, 31, 40, 31] produces ["Trace=10", "Warn=31", "?=40", "Warn=31"]. Test inputs represent bytes as JSON integers from 0 through 255.

A discriminant identifies a variant

Lvl declares Trace = 10, Debug = 20, Info = 30, Warn without an explicit value, and Error = 50. An implicit discriminant is the previous variant's value plus one, so Warn is 31. If the first variant has no explicit value, its discriminant starts at zero. Duplicate values are rejected with E0081.

For this unit-only enum, #[repr(u8)] fixes the representation to u8. It does not make every byte a valid Lvl. Only the declared variants are valid. Lvl::Info can be cast to u8 to obtain 30, but u8 cannot be cast back to Lvl.

This complete, smaller example shows explicit and implicit values:

#[repr(u8)]
enum Status { Ready = 7, Busy, Done }

fn main() {
    assert_eq!(Status::Ready as u8, 7);
    assert_eq!(Status::Busy as u8, 8);
    assert_eq!(Status::Done as u8, 9);
}

Representation and enum-to-integer casting have additional rules for other enum shapes. See the enum reference.

Decode with a fallible conversion

The starter's Some(code as Self) fails with E0605. A numeric cast does not validate the input against the enum's variants. Implement from_u8 as a match returning Some of the corresponding variant for each recognized byte and None for every other byte. Keep the default arm; unknown protocol values should be reported, not treated as a valid level or made to panic.

The supplied decode_levels function already formats Some and None correctly. Its Debug formatting uses the variant names, and its enum-to-u8 cast reports the discriminant. You only need to repair the conversion method.

Rust has no built-in derive that generates this reverse mapping. External procedural macros can provide conversions in other projects; this exercise uses an explicit match and the standard library. A separate API could instead implement TryFrom<u8> with an error type, but keep Option<Self> here.

Compare variants without comparing payloads

An enum with payload fields cannot be converted to an integer with this numeric cast. To compare which variant is present while ignoring its data, use mem::discriminant. This complete example compares variants and separately reads the payload:

use std::mem::discriminant;

enum Message { Number(u8), End }

fn main() {
    let first = Message::Number(1);
    let second = Message::Number(9);
    assert_eq!(discriminant(&first), discriminant(&second));
    assert_ne!(discriminant(&first), discriminant(&Message::End));
    if let Message::Number(value) = first {
        assert_eq!(value, 1);
    }
}

The returned Discriminant value is an opaque token for variant comparison, not a numeric protocol code. Use the explicit decoder for incoming bytes and the defined Lvl cast for outgoing codes.

Passing requires the exact output strings, successful compilation and Clippy with warnings treated as errors.