Step 4 of 6
Nested enums and ignoring parts
Patterns nest exactly as deep as your data does. An enum variant can hold a struct-like set of fields, and those fields can hold other enums; one pattern can look at all of it.
enum Button {
Left,
Right,
}
enum Event {
Key(char),
Click { x: i32, y: i32, button: Button },
Scroll(i32),
Quit,
}
fn handle(event: &Event) -> String {
match event {
Event::Key('q') | Event::Quit => String::from("quit"),
Event::Key(c @ '0'..='9') => format!("digit {c}"),
Event::Key(c) => format!("key {c}"),
Event::Click { button: Button::Right, .. } => String::from("open menu"),
Event::Click { x, y, button: Button::Left } => format!("left click at ({x}, {y})"),
Event::Scroll(0) => String::from("no scroll"),
Event::Scroll(n) if *n > 0 => format!("scroll down {n}"),
Event::Scroll(n) => format!("scroll up {}", -n),
}
}
fn main() {
let events = [
Event::Key('7'),
Event::Key('w'),
Event::Click { x: 10, y: 20, button: Button::Left },
Event::Click { x: 5, y: 5, button: Button::Right },
Event::Scroll(-3),
Event::Scroll(4),
Event::Key('q'),
Event::Quit,
];
for e in &events {
println!("{}", handle(e));
}
}
digit 7
key w
left click at (10, 20)
open menu
scroll up 3
scroll down 4
quit
quit
Piece by piece:
Event::Key('q') | Event::Quitcombines a variant with a literal inside it and another variant, with|.Event::Click { button: Button::Right, .. }looks inside the click at itsbuttonfield and checks it'sButton::Right. The..ignoresxandy.Event::Click { x, y, button: Button::Left }bindsxandyand requires a left button.Event::Scroll(n) if *n > 0: sincehandletakes&Event,nis a&i32. Comparing needs the number itself, so the guard says*n. Printing works on the reference directly.- The compiler checks the whole nested structure for exhaustiveness: remove the
Button::Leftarm and it tells you that&Event::Click { button: Button::Left, .. }isn't covered (with a&, becausehandlematches on a reference).
_, _name and ..
| Pattern | Meaning |
|---|---|
_ |
match one value, don't bind it (nothing is moved or borrowed) |
_name |
bind the value (it is moved or borrowed) but don't warn if it's unused |
.. |
skip all remaining fields or elements |
Common mistake: moving out while matching
Matching on an owned value with a binding moves the data out of it:
fn main() {
let name: Option<String> = Some(String::from("Ferris"));
match name {
Some(n) => println!("hi {n}"),
None => println!("nobody"),
}
println!("{:?}", name);
}
error[E0382]: borrow of partially moved value: `name`
Some(n) moved the String into n, so name is no longer whole. Match on a reference instead (match &name), and n becomes a &String. If you don't need the value at all, Some(_) doesn't move anything.
Your task
A checkout supports three payment methods. Write fn total_charge(p: &Payment) -> u32, the amount charged in cents including the card fee:
Cash(cents): justcents- an
Amexcard:cents + cents * 3 / 100(3% fee) - any other card:
cents + cents / 100(1% fee) Voucher: 0
Challenge 2
Fill in the three blanks: match the nested Status::Active, bind the days field of Suspended, and ignore the other fields of a closed account.
Challenge 3
A warehouse robot gets commands. Write fn command_label(cmd: &Command) -> String:
- a
Movewith 0 steps (any direction):"stay" Movenorth N:"up N"; south:"down N"; east or west:"sideways N"Wait(s):"wait Ns"(for example"wait 5s")Stop:"stop"
Previous: Destructuring structs and tuples Next: while let and slice patterns