4.4 KiB
% Match
Often, a simple if
/else
isn't enough, because you have more than two
possible options. Also, else
conditions can get incredibly complicated, so
what's the solution?
Rust has a keyword, match
, that allows you to replace complicated if
/else
groupings with something more powerful. Check it out:
let x = 5;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
4 => println!("four"),
5 => println!("five"),
_ => println!("something else"),
}
match
takes an expression and then branches based on its value. Each arm of
the branch is of the form val => expression
. When the value matches, that arm's
expression will be evaluated. It's called match
because of the term 'pattern
matching', which match
is an implementation of.
So what's the big advantage here? Well, there are a few. First of all, match
enforces exhaustiveness checking. Do you see that last arm, the one with the
underscore (_
)? If we remove that arm, Rust will give us an error:
error: non-exhaustive patterns: `_` not covered
In other words, Rust is trying to tell us we forgot a value. Because x
is an
integer, Rust knows that it can have a number of different values – for example,
6
. Without the _
, however, there is no arm that could match, and so Rust refuses
to compile. _
acts like a catch-all arm. If none of the other arms match,
the arm with _
will, and since we have this catch-all arm, we now have an arm
for every possible value of x
, and so our program will compile successfully.
match
statements also destructure enums, as well. Remember this code from the
section on enums?
use std::cmp::Ordering;
fn cmp(a: i32, b: i32) -> Ordering {
if a < b { Ordering::Less }
else if a > b { Ordering::Greater }
else { Ordering::Equal }
}
fn main() {
let x = 5;
let y = 10;
let ordering = cmp(x, y);
if ordering == Ordering::Less {
println!("less");
} else if ordering == Ordering::Greater {
println!("greater");
} else if ordering == Ordering::Equal {
println!("equal");
}
}
We can re-write this as a match
:
use std::cmp::Ordering;
fn cmp(a: i32, b: i32) -> Ordering {
if a < b { Ordering::Less }
else if a > b { Ordering::Greater }
else { Ordering::Equal }
}
fn main() {
let x = 5;
let y = 10;
match cmp(x, y) {
Ordering::Less => println!("less"),
Ordering::Greater => println!("greater"),
Ordering::Equal => println!("equal"),
}
}
This version has way less noise, and it also checks exhaustively to make sure
that we have covered all possible variants of Ordering
. With our if
/else
version, if we had forgotten the Greater
case, for example, our program would
have happily compiled. If we forget in the match
, it will not. Rust helps us
make sure to cover all of our bases.
match
expressions also allow us to get the values contained in an enum
(also known as destructuring) as follows:
enum OptionalInt {
Value(i32),
Missing,
}
fn main() {
let x = OptionalInt::Value(5);
let y = OptionalInt::Missing;
match x {
OptionalInt::Value(n) => println!("x is {}", n),
OptionalInt::Missing => println!("x is missing!"),
}
match y {
OptionalInt::Value(n) => println!("y is {}", n),
OptionalInt::Missing => println!("y is missing!"),
}
}
That is how you can get and use the values contained in enum
s.
It can also allow us to handle errors or unexpected computations; for example, a
function that is not guaranteed to be able to compute a result (an i32
here)
could return an OptionalInt
, and we would handle that value with a match
.
As you can see, enum
and match
used together are quite useful!
match
is also an expression, which means we can use it on the right-hand
side of a let
binding or directly where an expression is used. We could
also implement the previous example like this:
use std::cmp::Ordering;
fn cmp(a: i32, b: i32) -> Ordering {
if a < b { Ordering::Less }
else if a > b { Ordering::Greater }
else { Ordering::Equal }
}
fn main() {
let x = 5;
let y = 10;
println!("{}", match cmp(x, y) {
Ordering::Less => "less",
Ordering::Greater => "greater",
Ordering::Equal => "equal",
});
}
Sometimes, it's a nice pattern.