Rust provides a match
statement that's used to match patterns against input values.
Match statements can return a value, which can be captured as a variable or be returned from a function.
Each match statement has multiple "arms" which must address all potential match conditions.
You can use the underscore _
as a "catch-all" arm in match statements.
fn main() { let age = 22u8; match age { 21.. => { } } }
If you don't specify all potential patterns in the match statement, you'll receive an error similar to the following. Check out the above example on the Rust Playground.
error[E0004]: non-exhaustive patterns:
i32::MIN..=21_i32
and23_i32..=i32::MAX
not covered
Let's make sure we satisfy all potential values for the age
input.
We are using the Rust range operator to account for:
- All valid values up to 21, and
- All valid values 21 and greater
fn main() { let age = 22u8; match age { ..21 => { } 21.. => { } } }
Error: Match Against Mismatched Types
In our earlier example, the Rust compiler knows that we are comparing the match arms to an unsigned 8-bit integer, because of the age
variable's data type.
Therefore, it wouldn't make sense to add a match arm that compares a u8
to a &str
value.
Let's try adding another match arm that compares the match input to a string slice.
Try this example on the Rust playground.
fn main() { let age = 22u8; match age { ..21 => { } 21.. => { } "25" => { } } }
When you try to compile and run this example, you'll receive an error message.
error[E0308]: mismatched types this expression has type
u8
expectedu8
, found&str
Whenever you're coding a match statement, you'll want to ensure that your match arms match the data type of the match input.
Return Value from Match Statement
As previously mentioned, you can also return values from match
statements.
All you need to do is use a let
statement to capture the returned value into a variable.
Notice that we do not put a semicolon after the return value from each match arm.
However, you must put a semicolon at the end of the match statement.
The allowed_to_drink
variable will contain a bool
value.
Check out this example on the Rust Playground.
#[tokio::main] async fn main() { let age = 22; // Try changing this to a different number, to change the result. let allowed_to_drink = match age { ..21 => { false } 21.. => { true } }; println!("Are you allowed to drink? {0}", allowed_to_drink); }
The result should look like this:
Are you allowed to drink? true
Return Match Value from Function
You can return a value from a match
statement directly to a function's return value.
Let's write a simple function that returns a bool
value, and embed the match statement inside it.
Notice that we do not put a semicolon at the end of the match statement.
This allows the match statement's return value to be passed up to the function's return value.
Here's the following example on the Rust Playground.
fn main() { let age01 = 22; let allowed_to_drink = test_age(age01); println!("Are you allowed to drink? {0}", allowed_to_drink); } fn test_age(age: u8) -> bool { match age { ..21 => { false } 21.. => { true } } // No semicolon here, which allows match statement to return its value to the function }
⚠️ If you did accidentally put a semicolon after the match statement's closing curly brace, you would receive this error:
error[E0308]: mismatched types expected
bool
, found()
implicitly returns()
as its body has no tail orreturn
expression
The Rust compiler is extremely helpful and tells us how to resolve the error!
help: remove this semicolon to return this value
Using a Catch-All Match Arm
In our earlier examples, we accounted for all possible matches by using only two match arms.
But what would happen if we didn't account for all values?
Let's say that from 18-20 years old, we want to print a statement that tells the user that they're close to being of drinking age, but still return false
.
First, we'll limit the first match arm up to age 18.
Then we'll add a "catch-all" arm using the underscore character _
that matches anything not accounted for.
Now if you change the age variable to 18, 19, or 20, you should see the message printed out to the terminal.
fn main() { let age = 20; // Close to drinking age let allowed_to_drink = match age { ..18 => { false } 21.. => { true } _ => { println!("You can't drink yet, but you're close to drinking age! 🍺"); false } }; println!("Are you allowed to drink? {0}", allowed_to_drink); }
The result from the example above will be:
You can't drink yet, but you're close to drinking age! 🍺
Are you allowed to drink? false
Ordering Match Statement Arms
In the catch-all example, we added an underscore as a wildcard that matches any input value to the match statement.
It's important to note that the order of match
arms affects your Rust code execution.
If you put the "catch-all" match as the first match arm, inside your match
block, then none of the other match arms will be executed.
In the following example, we will put the wildcard match arm first. The Rust compiler will allow this, but it will also throw a warning about the unreachable match arms. Remember that only the first matching match arm will be executed. Once a match arm has been matched by the input value, all of the other match arms are ignored.
fn main() { let age = 25; // Even though we are old enough, we never reach the 3rd match arm let allowed_to_drink = match age { _ => { println!("You can't drink yet, but you're close to drinking age! 🍺"); false } ..18 => { false } 21.. => { true } }; println!("Are you allowed to drink? {0}", allowed_to_drink); }
The result from this program will be:
You can't drink yet, but you're close to drinking age! 🍺
Are you allowed to drink? false
Obviously the false
result is unexpected, since 25 is old enough to consume alcohol.
This would be considered a bug in our program.
Thankfully the Rust compiler warns us about the bug.
warning: unreachable pattern
While the underscore being used as an overlapping match arm is extreme, you can also have more limited overlapping match arms.
Try the following example on the Rust Playground.
You'll notice that the 25
match arm will never be reached, because the 21..
match arm already accounts for the value 25
.
fn main() { let age = 25; let allowed_to_drink = match age { ..18 => { false } 21.. => { true } 25 => { println!("You're 25 and allowed to drink!"); true } _ => { println!("You can't drink yet, but you're close to drinking age! 🍺"); false } }; println!("Are you allowed to drink? {0}", allowed_to_drink); }