Rust match expression
last modified October 25, 2026
In this article we show how to use the match expression in Rust.
The match expression compares a value against a series of
patterns and runs the code of the first pattern that matches. It resembles the
switch statement of other languages, but it is more powerful: the
patterns can destructure enums, tuples, structs, and slices, bind parts of the
value to variables, and test ranges and extra conditions.
match value {
pattern1 => expression1,
pattern2 => expression2,
_ => default_expression,
}
Each pattern => expression pair is called an arm.
The arms are tested from top to bottom. The patterns must be
exhaustive: together they have to cover every possible value,
otherwise the program does not compile. The underscore _ is a
wildcard pattern that matches any value.
The examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q.
Simple example
The following example matches integer literals.
fn main() {
for number in 1..=4 {
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Other"),
}
}
}
We go over the numbers 1 to 4 and print a word for each of them.
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Other"),
}
The value of number is compared with the literals 1, 2, and 3.
For 4, none of them matches and the wildcard arm is executed. Without the
_ arm, the compiler would reject the code, because an
i32 has many more possible values.
$ cargo run -q One Two Three Other
Match as an expression
A match produces a value: the value of the executed arm. All arms
must have the same type.
fn main() {
let day = 6;
let kind = match day {
1..=5 => "workday",
6 | 7 => "weekend",
_ => "invalid day",
};
println!("Day {day} is a {kind}");
}
We assign the result of the match to the kind
variable.
1..=5 => "workday", 6 | 7 => "weekend",
The 1..=5 pattern matches a range of values, and the
| operator combines several patterns in one arm.
$ cargo run -q Day 6 is a weekend
Range patterns
Range patterns work with integers and characters. An inclusive range is
written as a..=b, an exclusive one as a..b; one of
the bounds can be omitted.
fn main() {
let temps = [-12, 0, 8, 21, 35];
for t in temps {
let desc = match t {
..0 => "freezing",
0..=10 => "cold",
11..=25 => "pleasant",
_ => "hot",
};
println!("{t:>3} °C: {desc}");
}
for c in ['a', 'Q', '7', '%'] {
let kind = match c {
'a'..='z' => "lowercase letter",
'A'..='Z' => "uppercase letter",
'0'..='9' => "digit",
_ => "other",
};
println!("{c}: {kind}");
}
}
We classify temperatures and characters.
..0 => "freezing", 0..=10 => "cold", 11..=25 => "pleasant", _ => "hot",
The ..0 pattern matches all values below zero. The ranges are
tested in order and the first matching arm wins.
'a'..='z' => "lowercase letter", 'A'..='Z' => "uppercase letter", '0'..='9' => "digit",
Character ranges are useful for simple classification of ASCII characters.
$ cargo run -q -12 °C: freezing 0 °C: cold 8 °C: cold 21 °C: pleasant 35 °C: hot a: lowercase letter Q: uppercase letter 7: digit %: other
Match guards
A match guard is an additional if condition after the
pattern. The arm is chosen only when both the pattern matches and the
condition is true.
fn main() {
let r = rand::random_range(-5..=5);
match r {
x if x > 0 => println!("The number {x} is positive"),
0 => println!("The number is zero"),
x => println!("The number {x} is negative"),
}
}
We use the rand crate to get a random number in the range -5 to
5. The crate is added to the project with cargo add rand.
x if x > 0 => println!("The number {x} is positive"),
The x pattern matches any value and binds it to the variable
x. The guard if x > 0 limits the arm to positive
numbers.
0 => println!("The number is zero"),
x => println!("The number {x} is negative"),
Zero is matched by its literal. The last arm catches all remaining values, which can only be negative numbers. The compiler does not evaluate guards when it checks exhaustiveness, so a catch-all arm is needed.
$ cargo run -q The number 2 is positive $ cargo run -q The number 3 is positive $ cargo run -q The number 4 is positive
Binding with @
The @ operator binds the matched value to a variable while
testing it against a pattern.
fn main() {
for age in [3, 15, 42, 130] {
match age {
n @ 0..=12 => println!("{n}: child"),
n @ 13..=19 => println!("{n}: teenager"),
n @ 20..=120 => println!("{n}: adult"),
n => println!("{n}: not a valid age"),
}
}
}
We classify ages and print each value in the message.
n @ 0..=12 => println!("{n}: child"),
The value is tested against the range 0..=12 and, if it matches,
bound to n. A plain range pattern does not give us access to the
value.
$ cargo run -q 3: child 15: teenager 42: adult 130: not a valid age
Matching enums
The match expression is the main tool for working with enums. The
patterns can extract the data stored in the variants.
enum Shape {
Circle(f64),
Rectangle { width: f64, height: f64 },
Point,
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle { width, height } => width * height,
Shape::Point => 0.0,
}
}
fn main() {
let shapes = [
Shape::Circle(1.5),
Shape::Rectangle { width: 3.0, height: 4.0 },
Shape::Point,
];
for s in &shapes {
println!("{:.2}", area(s));
}
}
The Shape enum has three variants: a tuple variant, a struct
variant, and a unit variant.
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle { width, height } => width * height,
Shape::Point => 0.0,
}
Each arm destructures one variant and binds its fields to variables. The
three arms cover all variants, so no wildcard is needed. If we add a new
variant to the enum later, the compiler points out every match
that does not handle it.
$ cargo run -q 7.07 12.00 0.00
Matching Option and Result
The Option and Result types are enums too. Matching
them is the basic way to handle missing values and errors.
fn main() {
let words = ["sky", "forest", "cup"];
match words.iter().position(|&w| w == "cup") {
Some(idx) => println!("cup found at index {idx}"),
None => println!("cup not found"),
}
for text in ["42", "forty-two"] {
match text.parse::<i32>() {
Ok(n) => println!("parsed {n}"),
Err(e) => println!("cannot parse {text:?}: {e}"),
}
}
}
We search for a word and parse two strings.
match words.iter().position(|&w| w == "cup") {
Some(idx) => println!("cup found at index {idx}"),
None => println!("cup not found"),
}
The position method returns an Option. The
Some(idx) pattern extracts the index.
match text.parse::<i32>() {
Ok(n) => println!("parsed {n}"),
Err(e) => println!("cannot parse {text:?}: {e}"),
}
The parse method returns a Result. We handle both
the success and the error case. More on this in
Rust Option type.
$ cargo run -q cup found at index 2 parsed 42 cannot parse "forty-two": invalid digit found in string
Matching tuples
Tuple patterns test several values at once.
fn main() {
let points = [(0, 0), (0, 7), (4, 0), (3, 3), (2, -5)];
for p in points {
match p {
(0, 0) => println!("{p:?} is the origin"),
(0, y) => println!("{p:?} is on the y axis at {y}"),
(x, 0) => println!("{p:?} is on the x axis at {x}"),
(x, y) if x == y => println!("{p:?} is on the diagonal"),
_ => println!("{p:?} is somewhere else"),
}
}
}
We describe the position of points in a plane.
(0, 0) => println!("{p:?} is the origin"),
(0, y) => println!("{p:?} is on the y axis at {y}"),
(x, 0) => println!("{p:?} is on the x axis at {x}"),
(x, y) if x == y => println!("{p:?} is on the diagonal"),
Literals in a tuple pattern must match exactly, while names bind the
corresponding values. The order of the arms matters: the origin
(0, 0) would also match the second and the third arm, so it is
tested first.
$ cargo run -q (0, 0) is the origin (0, 7) is on the y axis at 7 (4, 0) is on the x axis at 4 (3, 3) is on the diagonal (2, -5) is somewhere else
Matching structs
Struct patterns test and extract individual fields.
struct User {
name: String,
age: u32,
active: bool,
}
fn main() {
let users = [
User { name: String::from("John Doe"), age: 34, active: true },
User { name: String::from("Roger Roe"), age: 17, active: true },
User { name: String::from("Lucy Smith"), age: 52, active: false },
];
for user in &users {
match user {
User { active: false, name, .. } => println!("{name}: inactive"),
User { age: 0..18, name, .. } => println!("{name}: minor"),
User { name, age, .. } => println!("{name}: adult, {age}"),
}
}
}
We sort users into three groups.
User { active: false, name, .. } => println!("{name}: inactive"),
User { age: 0..18, name, .. } => println!("{name}: minor"),
A field can be compared with a literal or a range, or bound to a variable of
the same name. The .. rest pattern ignores the remaining
fields. Since we match a reference, the bound variables are references to the
fields.
$ cargo run -q John Doe: adult, 34 Roger Roe: minor Lucy Smith: inactive
Matching slices
Slice patterns match by the number of elements and can extract the first and last ones.
fn describe(vals: &[i32]) -> String {
match vals {
[] => String::from("empty"),
[x] => format!("one element: {x}"),
[first, second] => format!("two elements: {first} and {second}"),
[first, .., last] => format!("{} elements, from {first} to {last}", vals.len()),
}
}
fn main() {
println!("{}", describe(&[]));
println!("{}", describe(&[5]));
println!("{}", describe(&[5, 6]));
println!("{}", describe(&[5, 6, 7, 8, 9]));
}
The describe function returns a different message depending on
the length of the slice.
[] => String::from("empty"),
[x] => format!("one element: {x}"),
[first, second] => format!("two elements: {first} and {second}"),
[first, .., last] => format!("{} elements, from {first} to {last}", vals.len()),
The patterns match an empty slice, a slice of one element, of two elements,
and of any other length. The .. in the last pattern stands for
any number of elements in the middle.
$ cargo run -q empty one element: 5 two elements: 5 and 6 5 elements, from 5 to 9
The matches! macro
The matches! macro tests whether a value matches a pattern and
returns a bool. It is a short form of a match with
true and false arms.
fn main() {
let chars = ['a', 'E', 'x', 'o', '1'];
for c in chars {
let vowel = matches!(c.to_ascii_lowercase(), 'a' | 'e' | 'i' | 'o' | 'u');
println!("{c}: {vowel}");
}
let vals = [3, 12, 7, 20];
let count = vals.iter().filter(|&&v| matches!(v, 10..=20)).count();
println!("{count} values between 10 and 20");
}
We test characters for vowels and count values in a range.
let vowel = matches!(c.to_ascii_lowercase(), 'a' | 'e' | 'i' | 'o' | 'u');
The macro returns true if the lowercased character is one of the
listed vowels.
let count = vals.iter().filter(|&&v| matches!(v, 10..=20)).count();
The macro is handy in closures that need a condition, such as the one passed
to filter.
$ cargo run -q a: true E: true x: false o: true 1: false 2 values between 10 and 20
Source
The Rust Programming Language: The match Control Flow Construct
The Rust Programming Language: Patterns and Matching
In this article we have worked with the match expression in
Rust.
Author
List all Rust tutorials.