Overview
match is an extremely powerful control flow operator in Rust for pattern matching and control flow selection. It allows a value to be compared with a series of patterns and executes the corresponding code based on the matching patterns. Patterns can be composed of literals, variables, wildcards, and many other things.
The pattern consists of (some combinations) of the following elements:
- Literal
- Deconstructed array
- Enumeration type
- Structure
- Processing conditional expressions
- Wildcard
- Placeholder
We can think of match expressions as some kind of coin classifier: a coin slides into a track with holes of different sizes, and each coin falls into a hole that matches its size. Likewise, the value passes through each pattern of the match, and when the first "compliant" pattern is encountered, the value enters the relevant code block and is used during execution.
Basic syntax
match value { pattern1 => { //code1 } pattern2 => { //code2 } _ => { //No match } }
In the basic syntax, value is the variable to match, pattern is the matching pattern, and => is followed by the code to be executed. The wildcard "-" (underscore) is used at the end to indicate other situations. If the value matches a certain pattern, the corresponding code block will be executed. If the value does not match any pattern, the default code block will be executed, that is ( _ => {...},).
Let's write a function to get an unknown coin and in a way similar to a banknote detector, determine what kind of coin it is and return its cent value.
// An enum and a match expression with enum members as patternenum 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, } }
Braces are usually not used if the branch code is short. If you want to run multiple lines of code in the branch, you can use braces.
fn value_in_cents(coin:Coin) -> u8 { match coin { Coin::Penny => { println!("Lucy penny!"); 1 } Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter =>25, } }
Bound value mode
Another useful feature of matching branches is that you can bind part of the values of the matching pattern. That is, how to extract values from enum members.
For example, let's modify a member in the enum to store data. Between 1999 and 2008, the United States printed a different design on one side of the 25-cent coin for each of 50 states. None of the other coins are designed to distinguish states, only these 25-cent coins have special value. This information can be added to our enum and includes a State value by changing the Quarter member.
//The Quarter member also stores a Coin enumeration with the UsState value.#[derive(Debug)] enum UsState { Alabama, Alaska, //--snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), }
Suppose a friend of ours is trying to collect 25 cents coins in all 50 states. While sorting change according to the type of coin, you can also report the state name corresponding to each 25-cent coin. If he does not have it, he can add it to his collection.
fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime =>10, Coin::Quarter(state) => { println!("State quarter from {:?}!", state); 25 } } }
Call value_in_cents(Coin::Quarter(UsState::Alaska), coin will be Coin::Quarter(UsState::Alaska). At this time, the value of the state binding is UsState::Alaska. Then use this binding in the pintln! expression to get the value of the internal state in the Quarter member of the Coin enumeration.
Match Option<T>
We want to write a function that takes an Option<i32> and adds it one if it contains a value. If there is no value in it, the function should return a value of None, and no attempt is made to perform any operation.
// A function using match expression on Option<i32>fn plus_one (x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i+1), } } let five = Some(5); let six =plus_one(five); let none = plus_one(None);
Match Some (T)
Matching is exhaustive
Rust knows we didn't cover all possible situations or even guide which patterns were forgotten! Matching in Rust is exhausted: the possibility must be exhausted to the last to make the code valid. Especially in the Option<T> example, Rust prevents us from forgetting to explicitly handle None, which saves us from assuming that we have a value that is actually empty, so that the error cannot happen.
Wildcards and _ placeholders
Suppose we are playing a game where if you roll the dice with a value of 3, the character will not move, but will get a novel hat. If the value of the dice is 7, your character will lose the novelty hat. For other values, your character will move the corresponding grid on the board.
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), other => move_player(other), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn move_player(num_spaces: u8) {}
This is a match that implements the above logic, the matching pattern is literals 3 and 7, and the last branch covers all other possible values, and the pattern is a variable we named other. The other branch's code uses this variable by passing it to the move_player function.
The last pattern does not list all possible values for u8, and the code can still be compiled. This wild-patch pattern meets the requirement that match must be exhausted, and wild-patch branches must be placed last because the patterns are matched in order.
When we do not want to use the values obtained by wildcard mode, we can use the _ mode. This pattern can match any value without binding to it.
Game changer: Now, when you roll out the value that feels is not 3 or 7, you have to throw it again. In this case we do not need to use this value. We modify the code to use "_" instead of the variable other:
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {}
The other values are explicitly ignored in the last branch, and the exhaustive requirement is also met.
Game changer: If you roll a value other than 3 or 7, nothing will happen to your turn. Code for using unit values (empty tuples) as _branch:
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}
This is the end of this article about the detailed explanation of the usage of the Rust control flow operator match. For more related contents of Rust control flow operator match, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!