We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 23 of 26
Discriminants, #[repr(u8)], as casts and mem::discriminant
Decode wire-format log-level bytes.
pub fn decode_levels(codes: Vec<u8>) -> Vec<String>
The protocol assigns Trace = 10, Debug = 20, Info = 30, Warn (which
takes the next value up from Info), Error = 50. A recognised code renders
as "{Name}={code}"; anything else renders as "?={code}".
[10, 31, 40] -> ["Trace=10", "Warn=31", "?=40"]
Discriminants: the bridge to the wire
A fieldless enum is, at the machine level, an integer. #[repr(u8)] pins
that integer to one byte and lets you choose the values:
#[repr(u8)]
enum Lvl {
Trace = 10,
Debug = 20,
Info = 30,
Warn, // 31 — implicit values continue from the last explicit one
Error = 50,
}
Warn is 31. Implicit discriminants continue from the previous explicit
one, they do not restart at zero. Two variants ending up with the same
value is E0081. Putting #[repr(u8)] somewhere it does not belong is
E0517.
Getting the number out is a cast: Lvl::Info as u8 is 30. That gives you
a real, C-compatible integer you can put in a byte buffer, hand to an FFI
function, or compare against a protocol constant.
The one-way street
Now try to go back:
let level = code as Lvl;
error[E0605]: non-primitive cast: `u8` as `Lvl`
The starter ships that error, and the asymmetry is the whole lesson. Casting
to an integer is always safe: every enum value has a discriminant. Casting
from an integer is not: 40 is not a Lvl, and a language that let you
produce one anyway would have handed you a value the exhaustiveness checker
had already promised could not exist. Every match in your program would be
a lie.
So there is no derive for this. You write the table by hand:
fn from_u8(code: u8) -> Option<Self> {
match code {
10 => Some(Self::Trace),
// ...
_ => None,
}
}
The Option in the return type is the point. Decoding an untrusted byte is a
fallible operation and the signature says so. In real projects people reach
for a derive macro crate to generate this; in this course there are no
crates, and writing it once is more instructive anyway.
Data-carrying enums are not integers at all
enum Data { A(u8), B }
Data::A(1) as u8 // E0605, non-primitive cast
The cast is only defined for enums where every variant is fieldless. A
Data::A(1) is a tag plus a payload; there is no integer it “is”. That
restriction is exactly why data-carrying enums are safe to match on.
If what you want is “are these the same variant, ignoring payloads”, the standard library has it:
use std::mem::discriminant;
discriminant(&Data::A(1)) == discriminant(&Data::A(9)) // true
mem::discriminant returns an opaque, comparable token. You cannot turn it
into a number, and that is deliberate — it is for comparison, not for the
wire.
Lints in the area
cast_possible_truncation and as_conversions (both allow-by-default,
pedantic/restriction) exist because as between integer types silently
truncates. as is the blunt instrument; u32::try_from(x) is the one that
tells you when it did not fit. This course’s later tracks lean on
try_from; enum-to-integer is one of the few places as is unambiguously
the right tool.
manual_range_patterns still applies to the match in from_u8 if your
codes happen to be contiguous.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.