Rust pattern matching
last modified November 13, 2026
In this article we look at pattern matching in Rust.
A pattern describes the shape of a value. Matching a value against a pattern does two things: it checks whether the value has that shape, and it binds parts of the value to variables. Patterns are made of literals, ranges, wildcards, variable names, and the structure of tuples, structs, enums, slices, and references.
Patterns appear in match arms, but also in
let statements, function parameters, for loops,
and in the if let, let else, and
while let constructs. The basics of match are
covered in Rust match expression;
this article focuses on the other forms and on the pattern syntax. The
examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q.
Where patterns appear
Many constructs that do not look like pattern matching take a pattern.
struct Point {
x: i32,
y: i32,
}
fn shift(Point { x, y }: Point, dx: i32) -> Point {
Point { x: x + dx, y }
}
fn main() {
let (a, b) = (1, 2);
println!("let: {a} {b}");
let Point { x, y } = shift(Point { x: 1, y: 5 }, 10);
println!("parameter: {x} {y}");
for (i, c) in "abc".chars().enumerate() {
print!("{i}{c} ");
}
println!();
let add = |(p, q): (i32, i32)| p + q;
println!("closure: {}", add((3, 4)));
let n = a + b;
let kind = match n {
0 => "zero",
1 | 2 => "small",
_ => "large",
};
println!("match: {kind}");
}
Each of these statements uses a pattern.
let (a, b) = (1, 2);
The left side of let is a pattern. A plain variable name is
the simplest pattern; it matches anything and binds it.
fn shift(Point { x, y }: Point, dx: i32) -> Point {
Function parameters are patterns. Here the struct is destructured right in the parameter list.
for (i, c) in "abc".chars().enumerate() {
The variable of a for loop is a pattern, which destructures
each tuple produced by the iterator. Closure parameters work the same
way.
$ cargo run -q let: 1 2 parameter: 11 5 0a 1b 2c closure: 7 match: large
Refutable and irrefutable patterns
A pattern that matches every possible value is
irrefutable; a pattern that can fail is
refutable. Plain let, function parameters, and
for loops accept only irrefutable patterns, because there is
nothing they could do when the match fails.
fn main() {
let input = "42";
let Ok(n) = input.parse::<i32>();
println!("{n}");
}
The parse method returns a Result, and the
Ok(n) pattern does not cover the Err case.
let Ok(n) = input.parse::<i32>();
The compiler rejects the statement with error E0005, even though the
string happens to be a valid number. It suggests let...else,
which we cover below.
$ cargo run -q
error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Ok(n) = input.parse::<i32>();
| ^^^^^ pattern `Err(_)` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
= note: the matched value is of type `std::result::Result<i32, std::num::ParseIntError>`
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Ok(n) = input.parse::<i32>() else { todo!() };
| ++++++++++++++++
For more information about this error, try `rustc --explain E0005`.
The if let expression
The if let expression runs a block when a refutable pattern
matches. It is a shorter form of a match with one interesting
arm.
use std::collections::HashMap;
fn main() {
let mut stock = HashMap::new();
stock.insert("apples", 12);
stock.insert("pears", 0);
for item in ["apples", "pears", "plums"] {
if let Some(&count) = stock.get(item) {
println!("{item}: {count}");
} else {
println!("{item}: unknown");
}
}
let values = ["7", "x", "-3"];
for v in values {
if let Ok(n) = v.parse::<i32>() && n > 0 {
println!("{v} is a positive number");
} else {
println!("{v} is not a positive number");
}
}
}
We look up items in a map and parse strings.
if let Some(&count) = stock.get(item) {
println!("{item}: {count}");
} else {
println!("{item}: unknown");
}
The get method returns Option<&i32>.
The Some(&count) pattern matches a present value and
copies the number out of the reference. The else branch
handles None.
if let Ok(n) = v.parse::<i32>() && n > 0 {
In the 2024 edition, if let can be combined with ordinary
conditions using &&. The variable n
bound by the pattern is available in the condition that follows.
$ cargo run -q apples: 12 pears: 0 plums: unknown 7 is a positive number x is not a positive number -3 is not a positive number
Let chains
A let chain joins several let patterns and
boolean conditions with &&. Before let chains, such
code needed nested if let blocks.
struct Config {
port: Option<String>,
debug: Option<String>,
}
fn describe(cfg: &Config) {
if let Some(p) = &cfg.port
&& let Ok(port) = p.parse::<u16>()
&& port >= 1024
&& let Some(d) = &cfg.debug
&& d == "on"
{
println!("debug server on port {port}");
} else {
println!("normal start");
}
}
fn main() {
let configs = [
Config { port: Some("8080".into()), debug: Some("on".into()) },
Config { port: Some("80".into()), debug: Some("on".into()) },
Config { port: Some("abc".into()), debug: None },
Config { port: None, debug: Some("on".into()) },
];
for c in &configs {
describe(c);
}
}
The server starts in debug mode only when the port is present, valid, not privileged, and the debug option is on.
if let Some(p) = &cfg.port
&& let Ok(port) = p.parse::<u16>()
&& port >= 1024
&& let Some(d) = &cfg.debug
&& d == "on"
{
The conditions are evaluated from left to right, and each one can use
the bindings of the previous ones. The first failing condition stops the
chain and the else branch runs. Let chains are available
since Rust 1.88 in the 2024 edition; || cannot be used
between let patterns.
$ cargo run -q debug server on port 8080 normal start normal start normal start
The let else statement
The let else statement binds variables from a refutable
pattern, or runs an else block when it does not match. The
else block must diverge: it has to return,
break, continue, or panic.
fn parse_pair(line: &str) -> Result<(String, u32), String> {
let Some((name, age)) = line.split_once(',') else {
return Err(format!("missing comma: {line:?}"));
};
let Ok(age) = age.trim().parse::<u32>() else {
return Err(format!("bad age: {age:?}"));
};
Ok((name.trim().to_string(), age))
}
fn main() {
let lines = ["Anna, 31", "Peter 45", "Lucia, old", "Bob,27"];
for line in lines {
match parse_pair(line) {
Ok((name, age)) => println!("{name} is {age}"),
Err(e) => println!("error: {e}"),
}
}
}
The function parses lines of the form name, age.
let Some((name, age)) = line.split_once(',') else {
return Err(format!("missing comma: {line:?}"));
};
If the line contains a comma, name and age are
bound for the rest of the function. Otherwise the function returns early.
let Ok(age) = age.trim().parse::<u32>() else {
return Err(format!("bad age: {age:?}"));
};
The new age shadows the string slice. Inside the
else block, the old age is still the string, so
it can be shown in the error message. This style keeps the happy path
unindented, unlike nested if let blocks.
$ cargo run -q Anna is 31 error: missing comma: "Peter 45" error: bad age: " old" Bob is 27
The while let loop
The while let loop runs as long as a pattern matches.
fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
print!("{top} ");
}
println!();
let text = "a1b22c333";
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
let mut digits = String::new();
while let Some(d) = chars.next_if(|ch| ch.is_ascii_digit()) {
digits.push(d);
}
println!("{c} -> {digits}");
}
let mut queue = vec![Some(5), Some(8), None, Some(1)];
queue.reverse();
while let Some(Some(job)) = queue.pop() {
println!("job {job}");
}
println!("left: {queue:?}");
}
We use it with a stack, a peekable iterator, and nested options.
while let Some(top) = stack.pop() {
The pop method returns None when the vector is
empty, which ends the loop.
while let Some(d) = chars.next_if(|ch| ch.is_ascii_digit()) {
The next_if method consumes the next character only if it
satisfies the predicate. The inner loop collects the digits following
each letter.
while let Some(Some(job)) = queue.pop() {
The pattern can be nested. The loop stops at the first
None job; that element has been popped, but the rest of the
queue stays.
$ cargo run -q 3 2 1 a -> 1 b -> 22 c -> 333 job 5 job 8 left: [Some(1)]
Literal and range patterns
Literals match exact values. Range patterns match a range of numbers or characters.
fn classify(c: char) -> &'static str {
match c {
'a' | 'e' | 'i' | 'o' | 'u' => "vowel",
'a'..='z' => "consonant",
'A'..='Z' => "uppercase",
'0'..='9' => "digit",
_ => "other",
}
}
fn grade(score: u32) -> char {
match score {
90.. => 'A',
80..90 => 'B',
70..80 => 'C',
60..70 => 'D',
..60 => 'F',
}
}
fn main() {
for c in ['e', 'k', 'Q', '7', '#'] {
println!("{c}: {}", classify(c));
}
let scores = [95, 83, 70, 61, 12];
let grades: String = scores.iter().map(|&s| grade(s)).collect();
println!("{grades}");
let temp = -3.5_f64;
let desc = match temp {
t if t < 0.0 => "freezing",
0.0..15.0 => "cold",
15.0..25.0 => "pleasant",
_ => "hot",
};
println!("{desc}");
}
We classify characters and convert scores to grades.
'a' | 'e' | 'i' | 'o' | 'u' => "vowel", 'a'..='z' => "consonant",
The | operator combines alternatives. The vowels also fall
into the 'a'..='z' range, but the first matching arm wins.
90.. => 'A', 80..90 => 'B', ... ..60 => 'F',
Exclusive ranges (80..90) and half-open ranges
(90.., ..60) are allowed in patterns. The
compiler checks that the ranges cover every u32, so no
wildcard arm is needed.
t if t < 0.0 => "freezing", 0.0..15.0 => "cold",
Floats can be matched against ranges too. The compiler cannot verify
that float ranges are exhaustive, so a final _ arm is
required.
$ cargo run -q e: vowel k: consonant Q: uppercase 7: digit #: other ABCDF freezing
Match guards
A match guard is an additional if condition after
a pattern. The arm is chosen only when both the pattern matches and the
guard is true.
fn describe(pair: (i32, i32)) -> String {
match pair {
(x, y) if x == y => format!("equal: {x}"),
(x, y) if x + y == 0 => format!("opposites: {x} {y}"),
(x, _) if x % 2 == 0 => format!("first is even: {x}"),
_ => "no match".to_string(),
}
}
fn main() {
for p in [(4, 4), (3, -3), (6, 1), (5, 2)] {
println!("{}", describe(p));
}
let limit = 10;
for v in [Some(3), Some(12), None] {
match v {
Some(n) if n > limit => println!("{n} is over the limit"),
Some(n) => println!("{n} is fine"),
None => println!("no value"),
}
}
let x = 4;
let flag = false;
match x {
2 | 4 | 9 if flag => println!("yes"),
_ => println!("no"),
}
}
Guards can compare the bound variables with each other and with other variables.
(x, y) if x == y => format!("equal: {x}"),
A pattern cannot express that two parts of a value are equal; a guard
can. Writing (x, x) is a compile error.
Some(n) if n > limit => println!("{n} is over the limit"),
A name in a pattern always creates a new binding; it is never compared
with an existing variable. To compare with limit, we need a
guard.
2 | 4 | 9 if flag => println!("yes"),
A guard applies to all alternatives of the pattern, not only to the last
one. The arm means (2 | 4 | 9) if flag.
$ cargo run -q equal: 4 opposites: 3 -3 first is even: 6 no match 3 is fine 12 is over the limit no value no
Bindings with @
The @ operator binds a value to a name while also testing it
against a pattern.
#[derive(Debug)]
enum Message {
Move { x: i32, y: i32 },
Write(String),
Quit,
}
fn age_group(age: u32) -> String {
match age {
n @ 0..=12 => format!("child ({n})"),
n @ 13..=19 => format!("teenager ({n})"),
n => format!("adult ({n})"),
}
}
fn main() {
for age in [7, 16, 42] {
println!("{}", age_group(age));
}
let msgs = [
Message::Move { x: 3, y: 0 },
Message::Move { x: 1, y: 7 },
Message::Write("hello".into()),
Message::Quit,
];
for m in &msgs {
match m {
Message::Move { x, y: y @ 1.. } => println!("move up by {y}, x={x}"),
mv @ Message::Move { .. } => println!("other move: {mv:?}"),
Message::Write(text) if text.len() < 10 => println!("short text {text}"),
other => println!("{other:?}"),
}
}
let v = vec![1, 2, 3, 4, 5];
if let [first, rest @ ..] = v.as_slice() {
println!("{first} then {rest:?}");
}
}
We bind numbers, struct fields, whole enum values, and slice rests.
n @ 0..=12 => format!("child ({n})"),
The value is checked against the range and, if it matches, bound to
n. Without @, we would know the range but not
the value.
Message::Move { x, y: y @ 1.. } => println!("move up by {y}, x={x}"),
mv @ Message::Move { .. } => println!("other move: {mv:?}"),
In the first arm, the y field must be positive. The second
arm binds the whole message to mv.
if let [first, rest @ ..] = v.as_slice() {
In slice patterns, rest @ .. binds the remaining elements as
a subslice.
$ cargo run -q
child (7)
teenager (16)
adult (42)
other move: Move { x: 3, y: 0 }
move up by 7, x=1
short text hello
Quit
1 then [2, 3, 4, 5]
Struct and enum patterns
Struct patterns name the fields to match. They can be nested to look deep into a value.
#[derive(Debug)]
struct Address {
city: String,
country: String,
}
#[derive(Debug)]
struct User {
name: String,
age: u32,
address: Option<Address>,
}
fn main() {
let users = vec![
User {
name: "Anna".into(),
age: 31,
address: Some(Address { city: "Bratislava".into(), country: "SK".into() }),
},
User { name: "Peter".into(), age: 17, address: None },
User {
name: "Lucia".into(),
age: 45,
address: Some(Address { city: "Vienna".into(), country: "AT".into() }),
},
];
for user in &users {
match user {
User { name, address: Some(Address { country, .. }), .. } if country == "SK" => {
println!("{name} lives in Slovakia")
}
User { name, age: 0..18, .. } => println!("{name} is a minor"),
User { name, address: Some(Address { city, .. }), .. } => {
println!("{name} lives in {city}")
}
User { name, .. } => println!("{name}: no address"),
}
}
let User { name, age, .. } = &users[2];
println!("{name} {age}");
}
We sort users into categories based on their age and address.
User { name, address: Some(Address { country, .. }), .. } if country == "SK" => {
The pattern reaches through the Option into the nested
Address. A field name alone, such as name, is a
shorthand for name: name. The .. skips the
remaining fields.
User { name, age: 0..18, .. } => println!("{name} is a minor"),
Field patterns can be literals or ranges, not just names.
let User { name, age, .. } = &users[2];
Struct patterns are irrefutable, so they can be used with
let. Destructuring a reference binds references to the
fields.
$ cargo run -q Anna lives in Slovakia Peter is a minor Lucia lives in Vienna Lucia 45
Slice patterns
Slice patterns match slices and arrays by their length and elements.
fn describe(v: &[i32]) -> String {
match v {
[] => "empty".to_string(),
[x] => format!("one element: {x}"),
[first, second] => format!("pair: {first} {second}"),
[first, .., last] => format!("{} elements, {first}..{last}", v.len()),
}
}
fn sum(v: &[i32]) -> i32 {
match v {
[] => 0,
[head, tail @ ..] => head + sum(tail),
}
}
fn command(args: &[&str]) -> String {
match args {
["add", name] => format!("adding {name}"),
["remove", names @ ..] if !names.is_empty() => format!("removing {}", names.join(", ")),
["list"] | [] => "listing".to_string(),
[cmd, ..] => format!("unknown command {cmd}"),
}
}
fn main() {
for v in [&[][..], &[5], &[1, 2], &[3, 6, 9, 12]] {
println!("{}", describe(v));
}
println!("sum: {}", sum(&[1, 2, 3, 4]));
for args in [&["add", "milk"][..], &["remove", "eggs", "salt"], &[], &["fly"]] {
println!("{}", command(args));
}
let arr = [10, 20, 30, 40];
let [a, b, rest @ ..] = arr;
println!("{a} {b} {rest:?}");
}
We describe slices of various lengths, sum a slice recursively, and parse commands.
[] => "empty".to_string(),
[x] => format!("one element: {x}"),
[first, second] => format!("pair: {first} {second}"),
[first, .., last] => format!("{} elements, {first}..{last}", v.len()),
Patterns without .. match an exact length. The last arm
matches any slice with at least two elements; together the arms cover all
lengths.
[head, tail @ ..] => head + sum(tail),
Splitting a slice into its head and tail is a natural fit for recursion.
["add", name] => format!("adding {name}"),
["remove", names @ ..] if !names.is_empty() => ...
Elements can be matched against string literals. This makes slice patterns handy for simple command-line parsing.
let [a, b, rest @ ..] = arr;
With an array, the length is known at compile time, so this pattern is
irrefutable and works with let. The rest is an array of two
elements.
$ cargo run -q empty one element: 5 pair: 1 2 4 elements, 3..12 sum: 10 adding milk removing eggs, salt listing unknown command fly 10 20 [30, 40]
Matching references
When a reference is matched against a non-reference pattern, Rust automatically dereferences it and binds the variables as references. This is known as default binding modes or match ergonomics.
fn main() {
let names = vec![Some(String::from("Anna")), None, Some(String::from("Bob"))];
for item in &names {
match item {
Some(name) => println!("{name} has {} letters", name.len()),
None => println!("missing"),
}
}
println!("{names:?}");
let mut counts = [Some(1), None, Some(5)];
for c in &mut counts {
match c {
Some(n) => *n *= 10,
None => *c = Some(0),
}
}
println!("{counts:?}");
let nums = [1, -2, 3, -4];
let pos: Vec<i32> = nums.iter().filter(|&&n| n > 0).copied().collect();
println!("{pos:?}");
let pair = (String::from("key"), 7);
match pair {
(ref k, n) if n > 5 => println!("{k} is big"),
_ => println!("small"),
}
println!("still usable: {pair:?}");
}
We match through shared and mutable references.
for item in &names {
match item {
Some(name) => println!("{name} has {} letters", name.len()),
The item is &Option<String>, so
name becomes &String. Nothing is moved and
the vector can be printed afterwards.
for c in &mut counts {
match c {
Some(n) => *n *= 10,
None => *c = Some(0),
}
}
Matching a mutable reference binds n as
&mut i32, which lets us change the value in place.
nums.iter().filter(|&&n| n > 0)
The filter closure receives &&i32. The
&&n pattern strips both references and copies the
number.
(ref k, n) if n > 5 => println!("{k} is big"),
When matching an owned value, the ref keyword binds a
reference instead of moving the field. Without it, the string would be
moved into k and pair could not be printed
afterwards.
$ cargo run -q
Anna has 4 letters
missing
Bob has 3 letters
[Some("Anna"), None, Some("Bob")]
[Some(10), Some(0), Some(50)]
[1, 3]
key is big
still usable: ("key", 7)
Matching strings
String literals in patterns have the type &str. To match
a String, we convert it to a string slice first.
fn respond(cmd: &str) -> &'static str {
match cmd.trim().to_lowercase().as_str() {
"hi" | "hello" => "Hello there!",
"bye" => "Goodbye!",
s if s.starts_with("help") => "Commands: hi, bye, help",
"" => "Say something.",
_ => "I do not understand.",
}
}
fn main() {
for input in ["Hello", " BYE ", "help me", "", "dance"] {
println!("{input:?} -> {}", respond(input));
}
let owned = String::from("n");
let answer = match owned.as_str() {
"yes" | "y" => "accepted",
"no" | "n" => "rejected",
_ => "unclear",
};
println!("{answer}");
}
A simple command responder normalizes its input before matching.
match cmd.trim().to_lowercase().as_str() {
The to_lowercase method returns a new String;
as_str turns it into &str, which can be
compared with literal patterns.
s if s.starts_with("help") => "Commands: hi, bye, help",
Patterns cannot match a prefix of a string. A binding with a guard does the job.
$ cargo run -q "Hello" -> Hello there! " BYE " -> Goodbye! "help me" -> Commands: hi, bye, help "" -> Say something. "dance" -> I do not understand. rejected
The matches! macro
The matches! macro tests whether a value matches a pattern
and returns a bool.
#[derive(Debug)]
enum Token {
Num(f64),
Op(char),
LParen,
RParen,
}
fn main() {
let tokens = [
Token::LParen,
Token::Num(2.0),
Token::Op('+'),
Token::Num(3.5),
Token::RParen,
Token::Op('*'),
Token::Num(4.0),
];
let numbers = tokens.iter().filter(|t| matches!(t, Token::Num(_))).count();
let ops: Vec<char> = tokens
.iter()
.filter_map(|t| match t {
Token::Op(c) => Some(*c),
_ => None,
})
.collect();
let parens = tokens.iter().filter(|t| matches!(t, Token::LParen | Token::RParen)).count();
let big = tokens.iter().any(|t| matches!(t, Token::Num(n) if *n > 3.0));
println!("numbers: {numbers}, ops: {ops:?}, parens: {parens}, big: {big}");
let c = 'k';
println!("{}", matches!(c, 'a'..='z' | '0'..='9' | '_'));
println!("{:?}", tokens.last());
}
We count and inspect tokens of a small expression.
tokens.iter().filter(|t| matches!(t, Token::Num(_))).count();
The macro is handy in closures passed to filter,
any, or all, where a full match
would be verbose.
tokens.iter().any(|t| matches!(t, Token::Num(n) if *n > 3.0));
The pattern can have alternatives and a guard. Bindings such as
n are visible only inside the guard.
$ cargo run -q numbers: 3, ops: ['+', '*'], parens: 2, big: true true Some(Num(4.0))
Or-patterns
The | operator can be used anywhere inside a pattern, not
only at the top level of a match arm.
enum Shape {
Circle(f64),
Square(f64),
Rect(f64, f64),
}
fn main() {
let shapes = [Shape::Circle(1.0), Shape::Square(2.0), Shape::Rect(2.5, 3.0)];
for s in &shapes {
let (Shape::Circle(width) | Shape::Square(width) | Shape::Rect(width, _)) = s;
let tall = matches!(s, Shape::Rect(w, h) if h > w);
println!("width: {width}, tall: {tall}");
}
for v in [Some(1), Some(4), Some(7), None] {
match v {
Some(1 | 3 | 5) => println!("{v:?} is small and odd"),
Some(n) if n < 6 => println!("{v:?} is small and even"),
Some(_) => println!("{v:?} is large"),
None => println!("nothing"),
}
}
let res: Result<u32, u32> = Err(3);
let (Ok(n) | Err(n)) = res;
println!("{n}");
}
We use or-patterns in let, nested in Some, and
across variants.
let (Shape::Circle(width) | Shape::Square(width) | Shape::Rect(width, _)) = s;
All alternatives bind the same variable with the same type. Together
they cover every variant, so the pattern is irrefutable and works in a
let statement. Parentheses are required around a top-level
or-pattern in let.
Some(1 | 3 | 5) => println!("{v:?} is small and odd"),
Nested alternatives avoid repeating Some in each case.
let (Ok(n) | Err(n)) = res;
When both variants of a Result hold the same type, this
extracts the value regardless of the variant.
$ cargo run -q width: 1, tall: false width: 2, tall: false width: 2.5, tall: true Some(1) is small and odd Some(4) is small and even Some(7) is large nothing 3
Exhaustiveness checking
A match must cover every possible value. The compiler checks
this and reports the missing cases.
enum Direction {
North,
East,
South,
West,
}
fn turn(d: Direction) -> Direction {
match d {
Direction::North => Direction::East,
Direction::East => Direction::South,
Direction::South => Direction::West,
}
}
fn main() {
let _ = turn(Direction::North);
}
The turn function forgets the West variant.
match d {
Direction::North => Direction::East,
Direction::East => Direction::South,
Direction::South => Direction::West,
}
Error E0004 names the missing pattern. This check is why it is better to
list enum variants explicitly than to end the match with _:
when a variant is added later, the compiler points to every match that
needs updating.
$ cargo run -q
error[E0004]: non-exhaustive patterns: `Direction::West` not covered
--> src/main.rs:9:11
|
9 | match d {
| ^ pattern `Direction::West` not covered
|
note: `Direction` defined here
--> src/main.rs:1:6
|
1 | enum Direction {
| ^^^^^^^^^
...
5 | West,
| ---- not covered
= note: the matched value is of type `Direction`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
12 ~ Direction::South => Direction::West,
13 ~ Direction::West => todo!(),
|
For more information about this error, try `rustc --explain E0004`.
Unreachable patterns
The compiler also detects arms that can never be reached.
fn main() {
let n: u8 = std::env::args().count() as u8;
let size = match n {
_ => "any",
0..=9 => "small",
};
println!("{size}");
}
The wildcard in the first arm matches everything, so the range arm below it never runs.
_ => "any", 0..=9 => "small",
This is a warning, not an error, and the program runs. It usually means that the arms are in the wrong order: more specific patterns must come before more general ones.
$ cargo run -q warning: unreachable pattern --> src/main.rs:5:9 | 4 | _ => "any", | - matches any value 5 | 0..=9 => "small", | ^^^^^ no value can reach this | = note: `#[warn(unreachable_patterns)]` (part of `#[warn(unused)]`) on by default any
Source
The Rust Book: Patterns and Matching
In this article we have worked with patterns in Rust. The basics of the
match expression are covered in
Rust match expression, and tuple
patterns in Rust tuples.
Author
List all Rust tutorials.