The match Control Flow Construct
In this chapter, we'll explore use match to compare values against patterns and execute code.
The match Control Flow Construct
Mana has a powerful control flow construct called match that allows you to compare a value against a series of patterns and execute code based on which pattern matches. Patterns can be made up of literal values, variable names, wildcards, and many other things.
The power of match comes from the expressiveness of the patterns and the fact that the compiler confirms all possible cases are handled.
Basic Match
Think of match like a coin-sorting machine: coins slide down a track with variously sized holes, and each coin falls through the first hole it fits:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
return match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}Each pattern is checked in order. When a pattern matches, the associated code executes.
Match Arms with Code Blocks
If you want to run multiple lines of code in a match arm, use curly brackets:
fn value_in_cents(coin: Coin) -> u8 {
return match coin {
Coin::Penny => {
println("Lucky penny!")
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}Patterns that Bind to Values
Match arms can bind to parts of the values that match the pattern:
enum UsState {
Alabama,
Alaska,
Arizona,
// ... etc
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
return match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println("State quarter from ", state.debug(), "!")
25
},
}
}When Coin::Quarter(state) matches, the state variable binds to the value of that quarter's state.
Matching with Option<T>
match is perfect for handling Option<T>:
fn plus_one(x: Option<int>) -> Option<int> {
return match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5)
let six = plus_one(five) // Some(6)
let nothing = plus_one(None) // NoneCombining match and enums is powerful. You'll see this pattern often in Mana code: match against an enum, bind a variable to the data inside, and execute code based on it.
Matches Are Exhaustive
Mana matches must be exhaustive—we must handle every possible case:
fn plus_one(x: Option<int>) -> Option<int> {
return match x {
Some(i) => Some(i + 1),
// Error: non-exhaustive patterns: `None` not covered
}
}Mana knows we didn't cover None and tells us which pattern we forgot. This exhaustiveness checking catches many potential bugs.
Catch-all Patterns and the _ Placeholder
For cases where we don't want to list every possible value, use a catch-all pattern:
let dice_roll = 9
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}The other pattern matches any value and binds it to a variable.
If we don't need the value, use _:
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}The _ pattern matches anything without binding.
If we want to do nothing for catch-all cases, use the unit value:
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}Multiple Patterns
Use | to match multiple patterns:
let x = 1
match x {
1 | 2 => println("one or two"),
3 => println("three"),
_ => println("anything"),
}Range Patterns
Match ranges of values with ..=:
let x = 5
match x {
1..=5 => println("one through five"),
_ => println("something else"),
}This also works with characters:
let c = 'c'
match c {
'a'..='j' => println("early ASCII letter"),
'k'..='z' => println("late ASCII letter"),
_ => println("something else"),
}Guards
Add extra conditions with if:
let num = Some(4)
match num {
Some(x) if x < 5 => println("less than five: ", x),
Some(x) => println(x),
None => (),
}The match guard if x < 5 is an additional condition that must be true for the arm to match.
Now let's learn about a more concise way to handle single patterns with if let.