Pattern matching
Enum variants can optionally include payloads.
enum Color {
Green,
Yellow,
Red,
Custom { red: u8, green: u8, blue: u8 }
}
let green: Color = Color::Green;
let yellow: Color = Color::Yellow;
let red: Color = Color::Red;
let blue: Color = Color::Custom { red: 0, green: 0, blue: 255 };
Here, the Red, Yellow, and Green variants work about the same way as before.
The Custom variant has a built-in struct with three u8
fields. We must
specify those fields when constructing a Color::Custom
variant, and we can
deconstruct them in a match
to read their values.
match current_color {
Color::Green => {
println!("green");
}
Color::Yellow => {
println!("yellow");
}
Color::Custom { red, green, blue } => {
println!("custom (RGB {}, {}, {})", red, green, blue);
}
}
This is known as pattern matching. Each of the conditions before a =>
arrow is called a pattern, and at runtime Rust will try checking each
pattern in turn to see which one matches. The first time it finds a match,
it will run the corresponding code and then exit the match
.
Matching multiple variants
You can match on a variant multiple times.
match current_color {
Color::Green => {
println!("green");
}
Color::Custom { red: 0, green, blue } => {
println!("custom color with no red (RGB 0, {}, {})", green, blue);
}
Color::Custom { red, green, blue } => {
println!("custom (RGB {}, {}, {})", red, green, blue);
}
}
Tuple payloads
If you don't want to name the fields in a payload, you can use a tuple payload:
enum Color {
Green,
Yellow,
Red,
Custom(u8, u8, u8)
}
In that case, when pattern matching on them, use a similar syntx to destructure the tuple payload:
match current_color {
Color::Green => {
println!("green");
}
Color::Yellow => {
println!("yellow");
}
Color::Red => {
println!("red");
}
Color::Custom(red, green, blue) => {
println!("custom (RGB {}, {}, {})", red, green, blue);
}
}
Runtime representation
As previously discussed, this enum would be a u8
at runtime.
enum Color {
Green,
Yellow,
Red,
Custom
}
Green
would be 0 at runtime, Yellow
would be 1, Red
would be 2, and
Custom
would be 3. If we introduce a payload, things change:
enum Color {
Green,
Yellow,
Red,
Custom(u8, u8, u8)
}
Now, Color::Custom
has a runtime representation of four u8
values. The
first u8
holds the number 4, just like before. That's the value that match
examines at runtime to be able to tell which variant this is, and we call that
value "thetdiscriminant." The other three u8
values are its payload.
This means that a Color::Custom
variant takes up 4 bytes at runtime: one byte
for the u8
discriminant, and three more bytes for its (u8, u8, u8)
payload.
A rule of enums is that each variant always takes up exactly the same amount
of memory, no matter what's in it. This means that because Color::Custom
takes
up 4 bytes at runtime, so do Color::Green
, Color::Yellow
, and Color::Red
,
even though they don't have payloads. Those variants consist of a u8
discriminant followed by three bytes of unused memory.
This is necessary because Rust has to know at compile time exactly how much memory to allocate for every value. Consider this function:
fn color_to_string(color: Color, max_string_length: u32) -> String
Rust needs to know how much memory, to reserve for the Color
argument, but
it can't know ahead of time what variant will go in there. This function could
be called with any Color
variant. To support all the variants, it needs to
have room for the largest variant, so the largest variant ends up determining
the size of all the variants. (This is true in other situations too, not just
function arguments.)
This has implications for performance: increasing the size of the largest variant in an enum increases the size of all the others as well, but increasing the size of a variant that isn't the largest has no effect on the sizes of any other variants.