Match
With Rust's match, we choose from a selection of expressions, and run the code from the matching expression. Only one path can be taken—a default case is possible.
We can assign a value to the result value of match—in this way match is like a function. And Options (Some) can be used in expressions.
To begin, we use both the match keyword, and the if-else
keywords to implement selection statements in Rust. The result of the 2 approaches here is the same.
if-else
block here contains the same logic as the match keyword. The code seems to be less elegant, and longer.fn main() { let id = 10; // Version 1: use match. match id { 5 => println!("Id is 5"), _ => println!("Id is not 5") } // Version 2: use if-else. if id == 5 { println!("Id is 5"); } else { println!("Id is not 5"); } }Id is not 5 Id is not 5
Match
can be used like an expression—it can be assigned to a variable. Here we have the string
"rust," and we match that string
in a match statement.
bool
"true" is used, and this value is stored in the memory location of the "compiled" variable.fn main() { let language = "rust"; // Assign a boolean based on the result of match. // ... Default is false. let compiled = match language { "rust" => true, "java" => true, _ => false }; println!("COMPILED: {}", compiled); }COMPILED: true
In Rust, many functions (like find) return an Option
. We can use pattern matching to match the possible options returned.
Find
here returns Some(1), which is matched in the match statement, and a message is printed.fn main() { let letters = "abc"; // Use match on an Option result. // ... Use Some() to match the returned option with its enclosed value. match letters.find("b") { Some(5) => println!("Found at index 5"), Some(1) => println!("Found at index 1"), _ => println!("Not found") } }Found at index 1
In Rust the match statement can handle groups of values in a single block. We match 2 cases in a single block by using the vertical bar.
fn main() { let value = 10; let result = match value { 5 => "Five", 0 | 10 => "Zero or ten", _ => "Unknown" }; // Print result. println!("VALUE = {}, RESULT = {}", value, result); }VALUE = 10, RESULT = Zero or ten
Inclusive ranges can be used as match conditions. Here we try to match the values 4 through 10 (including 10) in the first match condition.
fn main() { let value = 11; // Match with ranges. let result = match value { 4..=10 => "Four through ten", 11..=20 => "Eleven through twenty", _ => "Default" }; println!("{}", result); }Eleven through twenty
String
matchWe cannot match on a String
, but we can match on a str
reference. To convert a String
to a str
reference, we can just take its reference.
test()
function matches on a str
reference, and returns a usize
value. In main()
we call test()
in 2 separate ways.fn test(value: &str) -> usize { // Match on string literal. match value { "bird" => 10, "frog" => 20, "cat" => 30, _ => 0 } } fn main() { // Use the match function. println!("FROG: {}", test("frog")); // Build up a string and pass the reference to it. let mut temp = String::new(); temp.push_str("cat"); println!("CAT: {}", test(&temp)); }FROG: 20 CAT: 30
Matches
macroIn Rust, macros end with the exclamation mark. We can rewrite a match expression that returns true and false with the matches macro.
fn main() { if let Ok(element) = "100".parse() { // True if 50, 100 or 150. let result = matches!(element, 50 | 100 | 150); println!("{}", result); } }true
In Rust a match must handle all possible values of a type. If it does not, we will get a non-exhaustive patterns compile-time error.
fn main() { let value = 0; // Must add the default case here to compile. match value { 1 => println!("ONE") } }error[E0004]: non-exhaustive patterns: i32::MIN..=0_i32 and 2_i32..=i32::MAX not covered help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms note: the matched value is of type i32
Match
, if benchmarkIs match faster than if? The compiler (and LLVM) may be able to optimize match better than if. In this benchmark, we have 2 versions of the same logic.
get_match()
, returns a value based on a match statement.if
-statement. The same values are returned as in version 1.if
-statement. It is worth using match on numbers when possible.parse()
to get the max and top values, so the compiler does not optimize out the loops.use std::time::*; fn get_match(x: usize) -> usize { match x { 1 | 2 | 3 => 1, 4 => 2, 5 => 3, 6 | 7 | 8 => 4, _ => 0 } } fn get_if(x: usize) -> usize { if x == 1 || x == 2 || x == 3 { 1 } else if x == 4 { 2 } else if x == 5 { 3 } else if x == 6 || x == 7 || x == 8 { 4 } else { 0 } } fn main() { if let Ok(max) = "100000000".parse() { if let Ok(top) = "10".parse() { let mut sum1 = 0; let mut sum2 = 0; // Version 1: use match. let t0 = Instant::now(); for _ in 0..max { for i in 0..top { sum1 += get_match(i); } } println!("{}", t0.elapsed().as_millis()); // Version 2: use if. let t1 = Instant::now(); for _ in 0..max { for i in 0..top { sum2 += get_if(i); } } println!("{}", t1.elapsed().as_millis()); println!("{} = {}", sum1, sum2); } } } 510 ms (match) 1049 ms (if) 2000000000 = 2000000000
Often we use match with Option
and Some, but we do not need to. Match
statements can replace if
-statements, and the resulting code is often clearer.