Step 1 of 6
Literals, ranges and |
In the enums module you used match to pick a branch for each variant. The left side of every arm (Some(x), None, _) is a pattern: a description of a shape a value might have. Rust compares the value with each pattern from top to bottom and runs the first arm that matches. Patterns can do much more than name variants, and this module covers every kind you'll meet at work.
Literals, alternatives and ranges
fn main() {
for code in [200, 201, 301, 404, 418, 503] {
let meaning = match code {
200 | 201 => "success",
300..=399 => "redirect",
404 => "not found",
400..=499 => "client error",
500..=599 => "server error",
_ => "unknown",
};
println!("{code}: {meaning}");
}
}
200: success
201: success
301: redirect
404: not found
418: client error
503: server error
Piece by piece:
404is a literal pattern: it matches exactly that value.200 | 201uses|(read it as "or") to let several patterns share one arm.300..=399is a range pattern: any value from 300 to 399, both ends included.500..600(no=) leaves out the end, just like ranges inforloops, and600..means "600 or more".- Order matters. 404 is also inside
400..=499, but the404arm comes first, so it wins. Put specific patterns before general ones. _is the wildcard: it matches anything and keeps nothing. Here it catches every code the other arms don't.
Ranges work on char too, which makes character classification short and readable:
fn kind(c: char) -> &'static str {
match c {
'a' | 'e' | 'i' | 'o' | 'u' => "vowel",
'a'..='z' => "consonant",
'0'..='9' => "digit",
' ' | '\t' | '\n' => "whitespace",
_ => "other",
}
}
fn main() {
for c in ['e', 'k', '7', ' ', '!'] {
println!("{:?}: {}", c, kind(c));
}
}
'e': vowel
'k': consonant
'7': digit
' ': whitespace
'!': other
The return type &'static str is the type of a string literal: text stored inside the program itself, which lives as long as the program does (that's what 'static says; module 24 explains it fully). It's the usual return type for a function that picks one of several fixed labels.
The compiler checks every number
A match must be exhaustive: some arm has to match every possible value. For a number, that means every value of its type, and the compiler tells you exactly which ones you forgot:
fn main() {
let n: u8 = 7;
let size = match n {
0 => "none",
1..=9 => "a few",
};
println!("{size}");
}
error[E0004]: non-exhaustive patterns: `10_u8..=u8::MAX` not covered
Add _ => "lots" (or 10..=u8::MAX) to cover the rest. This check is why match is safer than a chain of ifs: when a new case appears, the compiler finds every place that must handle it.
Common mistakes
- General arms before specific ones. If
400..=499came before404, the404arm could never run. The compiler warnsunreachable pattern; that warning almost always means a bug. ..instead of..=.1..9stops at 8. Grades, ages and percentages usually want both ends:90..=100.- Expecting a variable name to compare.
match code { limit => ... }does not compare with a variable calledlimit: a plain name in a pattern is a new variable that matches everything. The next step shows how to compare with a variable.
Your task
Fill in the blanks so level turns a battery percentage into a label: 0 is "empty", 1 to 20 is "low", 21 to 99 is "ok", 100 is "full", and anything above 100 is "invalid".
Challenge 2
A cinema charges by age. Write fn ticket_price(age: u32) -> u32 with a match and range patterns:
| Age | Price |
|---|---|
| 0 to 12 | 5 |
| 13 to 17 | 8 |
| 18 to 64 | 12 |
| 65 and up | 6 |
Challenge 3
Write a whole program that reads one line of text and counts its characters by kind, after turning the line into lowercase:
- vowels:
a e i o u - consonants: the other letters
atoz - digits:
0to9 - spaces:
' ' - other: everything else (punctuation and so on)
For the input Hello World 42! print:
vowels: 3
consonants: 7
digits: 2
spaces: 2
other: 1
Ignore the newline at the end of the line (use trim_end(), which removes whitespace at the end only).