Step 3 of 6
Destructuring structs and tuples
Destructuring means taking a compound value apart into its pieces with a pattern. You've done it with tuples (let (a, b) = pair;). The same idea works for structs, and in every place a pattern can go: let, match arms, for loops and function parameters.
struct Point {
x: i32,
y: i32,
}
fn locate(p: &Point) -> String {
match p {
Point { x: 0, y: 0 } => String::from("at the origin"),
Point { x: 0, y } => format!("on the y axis at {y}"),
Point { x, y: 0 } => format!("on the x axis at {x}"),
Point { x, y } => format!("at ({x}, {y})"),
}
}
fn main() {
let p = Point { x: 3, y: -7 };
let Point { x, y } = p;
println!("x = {x}, y = {y}");
let Point { x: col, y: row } = p;
println!("col {col}, row {row}");
for q in [Point { x: 0, y: 0 }, Point { x: 0, y: 5 }, Point { x: 2, y: 0 }, p] {
println!("{}", locate(&q));
}
}
x = 3, y = -7
col 3, row -7
at the origin
on the y axis at 5
on the x axis at 2
at (3, -7)
Piece by piece:
let Point { x, y } = p;is shorthand forlet Point { x: x, y: y } = p;: it makes two variables named after the fields.Point { x: col, y: row }binds the fields to new names. Readx: colas "fieldxgoes intocol".- In a
match, a field can hold any pattern:x: 0only matches whenxis zero, while plainybinds whatever is there. locatematches on a reference (&Point). Rust then makes the bindings references too (yis a&i32), so nothing is moved out of the borrowed struct. Printing works the same; for arithmetic or comparisons you may need*y.
Matching on several values at once
Put values in a tuple and match on the tuple. This replaces nested ifs when a decision depends on two things:
fn main() {
for n in 9..=15 {
let text = match (n % 3, n % 5) {
(0, 0) => String::from("FizzBuzz"),
(0, _) => String::from("Fizz"),
(_, 0) => String::from("Buzz"),
_ => n.to_string(),
};
print!("{text} ");
}
println!();
}
Fizz Buzz 11 Fizz 13 14 FizzBuzz
(0, _) means "first part is 0, second part is anything".
Ignoring fields with ..
.. means "and the rest, which I don't care about". It keeps patterns short when a struct has many fields:
let Order { id, total, .. } = order; // ignore every other field
let (first, ..) = (1, 2, 3, 4); // just the first element
Common mistakes
Leaving out a field. A struct pattern must mention every field, or end with
..:struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 1, y: 2 }; let Point { x } = p; println!("{x}"); }error[E0027]: pattern does not mention field `y`Write
let Point { x, .. } = p;.Wrong tuple size.
let (a, b) = (1, 2, 3);fails withmismatched types; the pattern must have the same number of parts (or use..).
Your task
Finish this rock-paper-scissors referee. Each input line holds two moves (rock, paper or scissors). Match on the tuple (moves[0], moves[1]) and print player 1, player 2 or draw for each line. String literals work as patterns: ("rock", "scissors") => .... Rock beats scissors, scissors beat paper, paper beats rock.
Challenge 2
Write fn summary(order: &Order) -> String using struct patterns:
- quantity 0:
"order ID: empty" - express orders:
"order ID: TOTAL cents (express)", where TOTAL isquantity * unit_cents + 500 - other orders:
"order ID: TOTAL cents", where TOTAL isquantity * unit_cents
Use .. in at least one arm to skip the fields it doesn't need. Because you match on &Order, the bound fields are references; quantity * unit_cents still works on them.
Challenge 3
Write fn quadrant(point: (i32, i32)) -> &'static str from scratch. Match on the tuple:
(0, 0):"origin"- x is 0:
"y axis"; y is 0:"x axis" - otherwise
"I","II","III"or"IV": I is x and y positive, II is x negative and y positive, III both negative, IV x positive and y negative.
Previous: Match guards and @ bindings Next: Nested enums and ignoring parts