This Is What I Know about match
December 16, 2021
I've been learning Rust for some time now, and really enjoying it - the static typing, the helpful compiler, the tooling, the documentation, just about everything. I think I'm in the neophyte stage. Anyway, this is my attempt to describe the match
keyword, mostly (but certainly not entirely) in my own words.
match
is the keyword for pattern matching - like case
in Bash and other languages, though a little more powerful. Like case
, it is similar to a series of if/else expressions, where you go down through them to control the flow of code. However, if
and else if
expressions must evaluate to a boolean value. The match
expression can evaluate to any type. Each possibility is an "arm" that has a pattern that is evaluated. All arms need to return something of the same type. The first pattern that is true has the code associated with it run (which follows a =>
symbol, potentially in a curly brace block, if longer than one line) - Rust will not examine any subsequent arms. Additionally, matches are exhaustive: every possible option must be handled, otherwise the code will not compile.
Use "match guards" to further refine what you are matching. This is done by following the pattern with a bool-type expression. See 2nd arm of the longer example below.
Here are some syntax options for the tests (the left side):
- just provide the value
x ..= y
- inclusive range from x to yx | y
(x or y)_
- any (this will often be done as the last arm to catch all other possibilities)
Here is an example from the Rust book, matching on enum variants:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25, // if this one, for instance, was not included, the code wouldn't compile
}
}
This example (from my solution on Exercism) shows a number of these concepts as well as the matches!
macro:
pub fn evaluate(inputs: &[CalculatorInput]) -> Option<i32> {
if inputs.is_empty() {
return None;
}
let mut rpn: Vec<i32> = vec![];
for each in inputs {
match each {
CalculatorInput::Value(x) => { // curly braces not necessary, but this shows the longer form
rpn.push(*x);
} // note the lack of comma compared to the shorthand form
_ if rpn.len() < 2 => return None, // match guard
_ => { // the reason for this is because the four other possibilities all require these temp1 and temp2 vars to be created, otherwise would have just done normal match
let temp2 = rpn.pop().unwrap();
let temp1 = rpn.pop().unwrap();
if matches!(each, CalculatorInput::Add) { // matches! macro
rpn.push(temp1 + temp2);
}
if matches!(each, CalculatorInput::Subtract) {
rpn.push(temp1 - temp2);
}
if matches!(each, CalculatorInput::Multiply) {
rpn.push(temp1 * temp2);
}
if matches!(each, CalculatorInput::Divide) {
rpn.push(temp1 / temp2);
}
}
}
}
if rpn.len() > 1 {
return None;
}
Some(rpn[0])
}
You can also assign the result from a match expression to a variable (example from Tim McNamara's Rust in Action, Ch. 2):
let needle = 42;
let haystack = [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862];
for item in &haystack {
let result = match item {
42 | 132 => "hit!", // 42 or 132
_ => "miss", // anything else
};
}
There is a shorthand expression when you care about only one of the cases and don't need to do anything for all others: if let
:
if let Some(3) = some_u8_value { // the "let" is confusing - seems like it would be better without it since you aren't assigning a variable - so worth thinking of this as if x = y
println!("three");
}
You can also use else
with this, when you want to define the behavior to be done instead of just no behavior.
Sources:
- https://doc.rust-lang.org/std/macro.matches.html
- https://doc.rust-lang.org/std/keyword.match.html
- https://doc.rust-lang.org/reference/expressions/match-expr.html
- https://doc.rust-lang.org/reference/expressions/match-expr.html#match-guards
- https://doc.rust-lang.org/stable/book/ch06-00-enums.html
- https://www.rustinaction.com/