What match is
You already know how to branch with if and else if. Rust has a second tool, match, that is often the better choice when you compare one value against several possibilities. It resembles switch in other languages but is stronger in two ways:
matchis an expression. The whole construct evaluates to a value, so you can bind it directly withlet.- The arms are patterns, not only single values. A range such as
90..=100matches every number from 90 up to and including 100.
let temp = 3;
let advice = match temp {
30..=45 => "stay in the shade",
10..=29 => "perfect day",
_ => "bring a jacket",
};
println!("{}", advice); // prints: bring a jacket
Each arm has the form pattern => value, and arms are separated by commas. Rust tries the arms from top to bottom, and the value of the first arm that matches becomes the value of the whole match. The let statement ends with a semicolon after the closing brace, because let advice = match ... ; is one statement.
Patterns you will use
- A literal such as
3matches exactly that value. - An inclusive range such as
10..=29matches everything from 10 to 29. - Alternatives joined with
|, such as1 | 7, match either value. - A name binds the matched value for use in the arm.
- The wildcard
_matches anything and binds nothing.
The compiler wants every case covered
A match must be exhaustive: its arms have to cover every possible value of the type. Match an i32 with only two ranges and the build stops:
error[E0004]: non-exhaustive patterns: `i32::MIN..=79_i32` and `101_i32..=i32::MAX` not covered
The message names the values that fall through: everything below 80 and everything above 100. The fix is a catch-all arm, _ => ..., which handles whatever the earlier arms did not. This check is a feature, not an obstacle. You can never forget a case silently, and when you later add a variant to an enum, the compiler points at every match that needs updating.
Matching an Option
A match is especially useful with values that may be missing. For a vector nums, the method nums.get(1) looks up index 1 safely. It returns Some(value) if the index exists and None if it does not. This two-case type is called Option. Indexing with nums[7] on a vector of three elements would panic, but nums.get(7) just gives None.
let msg = match nums.get(1) {
Some(v) => format!("found {}", v),
None => String::from("nothing"),
};
The pattern Some(v) does more than test: it unpacks the inner value into v, which the arm can then use. All arms must produce the same type. format! returns an owned String, so the None arm uses String::from("nothing") as well. Writing a bare "nothing" there gives error[E0308]: `match` arms have incompatible types, because the other arm is a String and the literal is a &str.
Common mistakes
- Missing the catch-all. The
non-exhaustive patternserror means some values are unhandled. Add_. - Putting
_first. Arms are tried in order, so a wildcard at the top catches everything and the arms below can never match. - Arms of different types. Every arm must produce the same type, as in the Option example above.
- Off-by-one in ranges.
80..=89includes 89, while80..89does not, and a range without=in a pattern leaves a gap. - Forgetting commas between arms, or the semicolon after the closing brace of a
let.
The lesson's exercise
The exercise has two parts and prints two lines.
- Create
scorewith the value87. - Write
let grade = match score { ... };so the chosen arm becomes the value ofgrade. - Add the arms
90..=100 => "A"and80..=89 => "B". The score 87 lands in the second one. - Add
_ => "C"so every other score is covered. - Print
grade =followed by the grade withprintln!and a{}placeholder. - Build
numsasvec![1, 2, 3]. - Match on
nums.get(1)intomsg, with aSome(v)arm that buildsformat!("found {}", v)and aNonearm that givesString::from("nothing"). - Print
msg =followed by the message.
let score = 87;
let grade = match score {
90..=100 => "A",
80..=89 => "B",
_ => "C",
};
println!("grade = {}", grade);
let nums = vec![1, 2, 3];
let msg = match nums.get(1) {
Some(v) => format!("found {}", v),
None => String::from("nothing"),
};
println!("msg = {}", msg);
The program prints:
grade = B
msg = found 2
Indexes start at 0, so nums.get(1) is the second element, 2. Other scores give A for 90 and 100, B for 80 and 89, and C for 79 and 101. With nums.get(7) the second line would be msg = nothing.
Key takeaways
matchis an expression, so its result can be bound withlet.- Arms are patterns, including inclusive ranges like
80..=89, tried from top to bottom. - The compiler requires every possible value to be covered;
_is the catch-all. - Use
matchon anOptionto handleSomeandNonein one place, and make all arms the same type.
