Rust Notes: match, if let, while let, and let-else
A practical reference to Rust pattern matching.
Rust Pattern Matching: match, if let, while let, and let-else
Rust’s match, if let, while let, and let-else are all built around the same fundamental feature:
pattern matching
Once patterns make sense, these constructs become much easier to understand.
The most important idea is:
1
2
3
4
5
6
7
8
value
|
v
pattern
|
+---- matches ----> execute code
|
+---- doesn't match -> another branch / stop
For example:
1
2
3
4
5
let value = Some(10);
if let Some(x) = value {
println!("{x}");
}
Here:
1
Some(x)
is a pattern.
If the value is:
1
Some(10)
the pattern matches and x becomes 10.
If the value is:
1
None
the pattern does not match.
1. match
match compares a value against multiple patterns.
Basic syntax:
1
2
3
4
5
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
Example:
1
2
3
4
5
6
7
8
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("something else"),
}
The patterns are:
1
2
3
4
1
2
3
_
Rust checks the patterns from top to bottom.
The first matching pattern is executed.
2. match Must Be Exhaustive
One of the most important properties of match is that it must handle every possible case.
This does not compile:
1
2
3
4
5
6
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
}
because number could be anything other than 1 or 2.
Usually _ is used to handle everything else:
1
2
3
4
5
match number {
1 => println!("one"),
2 => println!("two"),
_ => println!("other"),
}
This exhaustive checking is one of the major benefits of match.
3. match Is an Expression
match does not only execute statements.
It can return a value.
1
2
3
4
5
6
7
8
9
10
let number = 3;
let message = match number {
1 => "one",
2 => "two",
3 => "three",
_ => "other",
};
println!("{message}");
Every branch must produce a compatible type.
For example:
1
2
3
4
5
let result = match number {
1 => 10,
2 => 20,
_ => 30,
};
The result is:
1
i32
4. Match Arms Can Have Blocks
A match arm can contain multiple statements:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
match number {
1 => {
println!("number is one");
println!("special case");
}
2 => {
println!("number is two");
}
_ => {
println!("other");
}
}
The last expression of the block becomes the value of the arm:
1
2
3
4
5
6
7
8
9
10
11
let result = match number {
1 => {
println!("one");
100
}
_ => {
println!("other");
0
}
};
5. _ Wildcard Pattern
The _ pattern matches anything.
1
2
3
4
match number {
1 => println!("one"),
_ => println!("not one"),
}
You can think of _ as:
1
anything else
It does not bind the value to a variable.
For example:
1
2
3
4
match value {
Some(x) => println!("{x}"),
_ => println!("something else"),
}
6. Binding a Value in a Pattern
A variable name in a pattern binds the matched value.
1
2
3
4
5
6
let value = Some(42);
match value {
Some(x) => println!("{x}"),
None => println!("nothing"),
}
When the value is:
1
Some(42)
the pattern:
1
Some(x)
matches and:
1
x = 42
7. Matching Option
Option<T> is one of the most common places to use match.
1
2
3
4
5
6
let value: Option<i32> = Some(10);
match value {
Some(x) => println!("value = {x}"),
None => println!("no value"),
}
Because Option has only two variants:
1
2
3
4
enum Option<T> {
Some(T),
None,
}
the match handles both possibilities.
8. Matching Result
Result<T, E> is another extremely common use.
1
2
3
4
5
6
let result: Result<i32, String> = Ok(10);
match result {
Ok(value) => println!("success: {value}"),
Err(error) => println!("error: {error}"),
}
This makes error handling explicit.
A common pattern:
1
2
3
4
match std::fs::read_to_string("file.txt") {
Ok(content) => println!("{content}"),
Err(error) => println!("failed: {error}"),
}
9. Matching Enums
Suppose we have:
1
2
3
4
5
6
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
We can match every variant:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
match message {
Message::Quit => {
println!("quit");
}
Message::Move { x, y } => {
println!("move to ({x}, {y})");
}
Message::Write(text) => {
println!("text: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("RGB: {r}, {g}, {b}");
}
}
This is one of the biggest strengths of Rust’s pattern matching.
10. Destructuring Tuples
Patterns can destructure tuples.
1
2
3
4
5
6
7
8
let point = (10, 20);
match point {
(0, 0) => println!("origin"),
(x, 0) => println!("on x axis: {x}"),
(0, y) => println!("on y axis: {y}"),
(x, y) => println!("point ({x}, {y})"),
}
The pattern:
1
(x, y)
extracts both values.
11. Destructuring Structs
Given:
1
2
3
4
struct User {
name: String,
age: u32,
}
we can write:
1
2
3
4
5
6
7
8
9
10
let user = User {
name: String::from("Amir"),
age: 29,
};
match user {
User { name, age } => {
println!("{name} is {age}");
}
}
You can also match specific fields:
1
2
3
4
5
6
7
8
9
match user {
User { age: 29, .. } => {
println!("age is 29");
}
_ => {
println!("different age");
}
}
12. .. in Patterns
.. means:
Ignore the remaining fields.
For structs:
1
2
3
4
5
6
7
8
match user {
User {
name,
..
} => {
println!("{name}");
}
}
For tuples:
1
2
3
4
5
let tuple = (1, 2, 3, 4, 5);
match tuple {
(first, ..) => println!("{first}"),
}
For arrays:
1
2
3
4
5
let values = [1, 2, 3, 4, 5];
match values {
[first, ..] => println!("{first}"),
}
13. Matching Multiple Values with |
The | operator creates an or-pattern.
1
2
3
4
5
match number {
1 | 2 | 3 => println!("small"),
4 | 5 | 6 => println!("medium"),
_ => println!("large"),
}
It means:
1
1 OR 2 OR 3
You can use it with enum variants:
1
2
3
4
5
match value {
Some(1) | Some(2) => println!("one or two"),
Some(x) => println!("{x}"),
None => println!("none"),
}
14. Binding with Or-Patterns
Bindings in an or-pattern must be consistent.
For example:
1
2
3
4
5
match value {
Some(1) | Some(2) => println!("one or two"),
Some(x) => println!("{x}"),
None => {}
}
Both alternatives in:
1
Some(1) | Some(2)
do not introduce bindings, so this is straightforward.
If alternatives introduce bindings, they must bind compatible names and types.
15. Matching Ranges
You can match ranges.
For integers:
1
2
3
4
5
match number {
1..=5 => println!("1 through 5"),
6..=10 => println!("6 through 10"),
_ => println!("other"),
}
..= is an inclusive range.
So:
1
1..=5
means:
1
1, 2, 3, 4, 5
16. Character Ranges
Ranges are also useful with char.
1
2
3
4
5
6
match character {
'a'..='z' => println!("lowercase"),
'A'..='Z' => println!("uppercase"),
'0'..='9' => println!("digit"),
_ => println!("other"),
}
17. Match Guards
A match pattern can have an additional condition called a guard.
Syntax:
1
PATTERN if CONDITION => EXPRESSION
Example:
1
2
3
4
match number {
x if x > 10 => println!("greater than 10"),
x => println!("{x}"),
}
The pattern:
1
x
matches the value.
Then the guard checks:
1
x > 10
18. Match Guards with Option
1
2
3
4
5
match value {
Some(x) if x > 10 => println!("large: {x}"),
Some(x) => println!("small: {x}"),
None => println!("none"),
}
The first arm only matches when:
1
2
3
value = Some(x)
AND
x > 10
19. Match Guards with |
You can combine patterns and conditions:
1
2
3
4
5
6
7
match number {
1 | 2 | 3 if number > 1 => {
println!("2 or 3");
}
_ => {}
}
Be careful with the precedence and meaning of guards on or-patterns.
For complex conditions, separate match arms are often easier to understand.
20. @ Bindings
The @ operator lets you both:
- test a pattern
- bind the matched value
Example:
1
2
3
4
5
6
7
match number {
value @ 1..=5 => {
println!("small number: {value}");
}
_ => {}
}
Without @, you can match:
1
1..=5
but you don’t automatically get the matched value as a named variable.
With:
1
value @ 1..=5
you get both.
21. @ with Enums
1
2
3
4
5
6
7
match message {
msg @ Message::Write(_) => {
println!("message = {msg:?}");
}
_ => {}
}
The whole matching value is bound to:
1
msg
while still requiring it to match:
1
Message::Write(_)
22. ref and References
Patterns can also work with references.
For example:
1
2
3
4
5
6
let value = 10;
let reference = &value;
match reference {
&x => println!("{x}"),
}
Modern Rust code often uses match ergonomics, so explicit ref is less common than it used to be.
For example:
1
2
3
4
5
6
let value = Some(String::from("hello"));
match &value {
Some(text) => println!("{text}"),
None => {}
}
Here text is automatically bound appropriately through match ergonomics.
23. if let
if let is useful when you care about only one pattern.
Instead of:
1
2
3
4
5
6
7
match value {
Some(x) => {
println!("{x}");
}
None => {}
}
you can write:
1
2
3
if let Some(x) = value {
println!("{x}");
}
The mental model is:
1
2
3
4
if the value matches this pattern
run this block
otherwise
do nothing
24. Basic if let Syntax
1
2
3
if let PATTERN = VALUE {
// pattern matched
}
Example:
1
2
3
if let Some(value) = option {
println!("{value}");
}
25. if let with else
You can handle the non-matching case:
1
2
3
4
5
if let Some(value) = option {
println!("value = {value}");
} else {
println!("no value");
}
Conceptually:
1
2
3
4
5
if let PATTERN = VALUE {
// matched
} else {
// didn't match
}
This is roughly equivalent to:
1
2
3
4
5
6
7
8
9
match VALUE {
PATTERN => {
// matched
}
_ => {
// didn't match
}
}
26. if let with else if
You can combine normal conditions and pattern matching:
1
2
3
4
5
6
7
if let Some(x) = value {
println!("some: {x}");
} else if condition {
println!("condition is true");
} else {
println!("nothing");
}
You can also use another if let:
1
2
3
4
5
6
7
if let Some(x) = value {
println!("some: {x}");
} else if let Some(y) = other {
println!("other: {y}");
} else {
println!("nothing");
}
27. if let Chains
Modern Rust also supports combining let conditions with other conditions.
Example:
1
2
3
if let Some(x) = value && x > 10 {
println!("large value: {x}");
}
You can combine multiple conditions:
1
2
3
4
5
6
if let Some(x) = value
&& x > 10
&& x < 100
{
println!("between 10 and 100");
}
This is useful when pattern matching is only one part of the condition.
28. if let with Multiple Patterns
Or-patterns can be used:
1
2
3
if let Some(1 | 2 | 3) = value {
println!("small");
}
You can also write:
1
2
3
if let Some(x @ 1..=5) = value {
println!("{x}");
}
29. When Should You Use match vs if let?
Use match when:
1
you need to handle multiple cases
Example:
1
2
3
4
match result {
Ok(value) => println!("success: {value}"),
Err(error) => println!("error: {error}"),
}
Use if let when:
1
you care primarily about one case
Example:
1
2
3
if let Ok(value) = result {
println!("success: {value}");
}
The trade-off is important:
1
2
3
4
5
6
7
8
9
match
exhaustive
explicit
better for many cases
if let
concise
convenient for one case
ignores other cases
30. while let
while let repeatedly matches a pattern.
Syntax:
1
2
3
while let PATTERN = VALUE {
// body
}
The loop continues while the pattern matches.
As soon as the pattern does not match, the loop stops.
31. Basic while let Example
A classic example is consuming an iterator:
1
2
3
4
5
let mut iterator = vec![1, 2, 3].into_iter();
while let Some(value) = iterator.next() {
println!("{value}");
}
Execution:
1
2
3
4
next() -> Some(1) -> body
next() -> Some(2) -> body
next() -> Some(3) -> body
next() -> None -> stop
This is why while let is especially useful with Option.
32. while let with Option
1
2
3
4
5
6
7
let mut value = Some(10);
while let Some(x) = value {
println!("{x}");
value = None;
}
The loop continues while:
1
value == Some(...)
and stops when:
1
value == None
33. while let with VecDeque
This is a very useful real-world pattern:
1
2
3
4
5
6
7
8
9
10
11
use std::collections::VecDeque;
let mut queue = VecDeque::from([
"task1",
"task2",
"task3",
]);
while let Some(task) = queue.pop_front() {
println!("processing {task}");
}
The loop naturally expresses:
1
2
3
while the queue contains a task
remove the task
process it
34. while let with Channels
while let is also common when receiving messages.
For example:
1
2
3
while let Some(message) = receiver.recv().await {
println!("{message}");
}
This is particularly common in async Rust.
The loop means:
1
2
3
4
5
while recv() returns Some(message)
process message
when recv() returns None
stop
35. while let vs loop + match
This:
1
2
3
while let Some(value) = iterator.next() {
println!("{value}");
}
can conceptually be written as:
1
2
3
4
5
6
7
8
9
10
11
loop {
match iterator.next() {
Some(value) => {
println!("{value}");
}
None => {
break;
}
}
}
while let is simply much cleaner when the loop condition itself is a pattern.
36. while let with Guards
while let is primarily pattern matching, but if additional conditions are required, a normal loop plus match or if is often clearer.
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
loop {
match iterator.next() {
Some(value) if value > 10 => {
println!("{value}");
}
Some(_) => {
break;
}
None => {
break;
}
}
}
This can be clearer than trying to make the loop condition overly complicated.
37. let-else
let-else is another pattern-matching construct.
It is especially useful when you want to extract a value or return early if the pattern doesn’t match.
Syntax:
1
2
3
let PATTERN = VALUE else {
// must diverge
};
Example:
1
2
3
4
5
let Some(value) = option else {
return;
};
println!("{value}");
If the pattern matches:
1
2
value is available
execution continues
If it does not:
1
2
else block executes
execution must leave the current flow
38. Why let-else Is Useful
Compare:
1
2
3
4
5
if let Some(value) = option {
println!("{value}");
} else {
return;
}
with:
1
2
3
4
5
let Some(value) = option else {
return;
};
println!("{value}");
The second form avoids an extra indentation level.
This is particularly useful for validation:
1
2
3
4
5
6
7
fn process(value: Option<String>) -> Option<usize> {
let Some(value) = value else {
return None;
};
Some(value.len())
}
39. let-else with Result
A common pattern:
1
2
3
4
5
6
7
8
9
fn process() -> Result<(), Error> {
let Ok(value) = get_value() else {
return Err(Error::InvalidValue);
};
println!("{value}");
Ok(())
}
It is useful when the successful path should remain at the main indentation level.
40. let Itself Uses Patterns
This is an important concept.
When you write:
1
let x = 10;
x is actually a pattern.
The syntax is conceptually:
1
let PATTERN = VALUE;
For example:
1
let (x, y) = (10, 20);
The pattern is:
1
(x, y)
It destructures the tuple.
Another example:
1
let Some(value) = option;
This would only be valid when the compiler can guarantee the pattern cannot fail.
For potentially failing patterns, use:
1
2
3
4
if let
while let
match
let-else
41. Irrefutable vs Refutable Patterns
This distinction explains why these constructs exist.
An irrefutable pattern must always match.
Example:
1
let x = 10;
x can match any value.
A tuple pattern:
1
let (x, y) = (10, 20);
also always matches a two-element tuple.
A refutable pattern can fail.
For example:
1
Some(x)
can fail because the value could be:
1
None
Therefore:
1
2
3
if let Some(x) = value {
// ...
}
is appropriate.
The mental model is:
1
2
3
4
5
6
7
8
9
10
irrefutable
cannot fail
ordinary let
refutable
may fail
match
if let
while let
let-else
42. Matching References
Suppose:
1
2
let value = Some(10);
let reference = &value;
You can match the reference:
1
2
3
4
match reference {
Some(x) => println!("{x}"),
None => println!("none"),
}
Rust’s match ergonomics automatically handle the reference in many cases.
You can also explicitly match a reference:
1
2
3
4
match reference {
&Some(x) => println!("{x}"),
&None => println!("none"),
}
The explicit form makes the dereferencing behavior visible.
43. Matching Nested Structures
Patterns can be nested.
1
2
3
4
5
6
7
let value = Some(Ok(10));
match value {
Some(Ok(x)) => println!("success: {x}"),
Some(Err(e)) => println!("error: {e}"),
None => println!("no value"),
}
This is one reason pattern matching is powerful.
You can inspect several layers of a data structure in one expression.
44. Nested if let
You can also use nested patterns:
1
2
3
if let Some(Ok(value)) = result {
println!("{value}");
}
This matches only when:
1
2
3
4
5
Some
|
+-- Ok
|
+-- value
45. Matching Strings
Rust does not normally pattern-match a String directly against string literals in the same way as enums or integers.
Usually you match a string slice:
1
2
3
4
5
match value.as_str() {
"start" => println!("starting"),
"stop" => println!("stopping"),
_ => println!("unknown command"),
}
Or:
1
2
3
4
5
match value.as_str() {
"get" | "read" => println!("read operation"),
"set" | "write" => println!("write operation"),
_ => println!("unknown"),
}
46. Matching Multiple Conditions
Sometimes match is cleaner than a long if/else chain.
Instead of:
1
2
3
4
5
6
7
8
9
if number == 1 {
println!("one");
} else if number == 2 {
println!("two");
} else if number == 3 {
println!("three");
} else {
println!("other");
}
you can write:
1
2
3
4
5
6
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("other"),
}
And patterns can be much more expressive than equality checks.
47. Match as a State Machine
match is especially useful when working with state machines.
For example:
1
2
3
4
5
enum State {
Starting,
Running,
Stopped,
}
Then:
1
2
3
4
5
match state {
State::Starting => start(),
State::Running => run(),
State::Stopped => stop(),
}
This is a very natural Rust design because adding a new enum variant can cause the compiler to identify matches that need updating.
48. Common Option Patterns
Extract only Some
1
2
3
if let Some(value) = option {
println!("{value}");
}
Handle both
1
2
3
4
match option {
Some(value) => println!("{value}"),
None => println!("none"),
}
Loop until None
1
2
3
while let Some(value) = iterator.next() {
println!("{value}");
}
Early return on None
1
2
3
let Some(value) = option else {
return;
};
These four constructs are closely related:
1
2
3
4
5
6
7
8
9
10
11
match
handle multiple cases
if let
handle one matching case
while let
repeatedly handle one matching case
let-else
extract a value or leave the current flow
49. Common Result Patterns
Handle both success and error
1
2
3
4
match result {
Ok(value) => println!("{value}"),
Err(error) => println!("{error}"),
}
Handle only success
1
2
3
if let Ok(value) = result {
println!("{value}");
}
Early return on error
1
2
3
let Ok(value) = result else {
return;
};
In real Rust code, the ? operator is often even better when the goal is simply to propagate an error:
1
let value = result?;
But let-else is useful when you need custom handling or when the pattern is more specific than just Ok/Err.
50. Pattern Matching with Ownership
Patterns interact with ownership and borrowing.
For example:
1
2
3
4
5
6
let value = Some(String::from("hello"));
match value {
Some(text) => println!("{text}"),
None => {}
}
Here the String can be moved into text.
Afterwards, value may no longer be usable.
To avoid moving the inner value, match a reference:
1
2
3
4
5
6
7
8
let value = Some(String::from("hello"));
match &value {
Some(text) => println!("{text}"),
None => {}
}
println!("{value:?}");
The important question when reading a pattern is therefore:
Am I moving the value, borrowing it, or copying it?
51. match and ref
Older Rust code may contain:
1
2
3
4
5
6
7
match value {
Some(ref text) => {
println!("{text}");
}
None => {}
}
The ref pattern binding creates a reference instead of moving the value.
Modern match ergonomics often allow simpler code:
1
2
3
4
5
6
7
match &value {
Some(text) => {
println!("{text}");
}
None => {}
}
When reading older Rust code, knowing ref is still useful.
52. Match Guards vs Patterns
Prefer a pattern when the condition naturally describes the structure:
1
2
3
4
match value {
Some(x) => println!("{x}"),
None => println!("none"),
}
Use a guard when the condition depends on a value:
1
2
3
4
5
match value {
Some(x) if x > 10 => println!("large"),
Some(x) => println!("small"),
None => println!("none"),
}
Think:
1
2
3
4
5
pattern
describes structure
guard
adds a boolean condition
53. match vs if
Use ordinary if when you are testing a boolean condition:
1
2
3
if x > 10 {
println!("large");
}
Use if let when you are testing whether a value matches a pattern:
1
2
3
if let Some(x) = value {
println!("{x}");
}
Use match when multiple patterns represent different cases:
1
2
3
4
5
match value {
Some(x) if x > 10 => println!("large"),
Some(x) => println!("small"),
None => println!("none"),
}
54. match vs if let vs while let vs let-else
| Construct | Purpose |
|---|---|
match | Handle multiple patterns |
if let | Handle one matching pattern conditionally |
while let | Repeatedly handle a matching pattern |
let-else | Extract a matching value or exit/diverge |
if | Handle boolean conditions |
A useful mental model:
1
2
3
4
5
6
7
8
9
10
11
12
13
Pattern Matching
|
+--------------+--------------+
| | |
match if let while let
| | |
many cases one case repeat case
|
exhaustive
let-else
|
extract or exit
55. Practical Decision Guide
When you see a value and need to decide what to do:
Multiple cases?
Use:
1
2
3
match value {
...
}
Only interested in one case?
Use:
1
2
3
if let PATTERN = value {
...
}
Want to keep doing something while the pattern matches?
Use:
1
2
3
while let PATTERN = value {
...
}
Want to extract a value and leave the function/block if it doesn’t match?
Use:
1
2
3
let PATTERN = value else {
return;
};
Just checking a boolean?
Use:
1
2
3
if condition {
...
}
56. Syntax Cheat Sheet
match
1
2
3
4
5
match value {
PATTERN => expression,
PATTERN => expression,
_ => expression,
}
Match with blocks
1
2
3
4
5
6
7
8
9
match value {
PATTERN => {
// statements
}
_ => {
// statements
}
}
Binding
1
2
3
4
match value {
Some(x) => println!("{x}"),
_ => {}
}
Or-pattern
1
2
3
4
match value {
1 | 2 | 3 => {}
_ => {}
}
Range
1
2
3
4
match value {
1..=10 => {}
_ => {}
}
Guard
1
2
3
4
match value {
x if x > 10 => {}
_ => {}
}
@ binding
1
2
3
4
match value {
x @ 1..=10 => {}
_ => {}
}
Struct destructuring
1
2
3
match value {
User { name, age } => {}
}
Tuple destructuring
1
2
3
match value {
(x, y) => {}
}
Nested pattern
1
2
3
4
5
match value {
Some(Ok(x)) => {}
Some(Err(e)) => {}
None => {}
}
if let
1
2
3
if let PATTERN = value {
// matched
}
if let with else
1
2
3
4
5
if let PATTERN = value {
// matched
} else {
// not matched
}
if let with else if
1
2
3
4
5
6
7
if let PATTERN = value {
// ...
} else if condition {
// ...
} else {
// ...
}
if let chain
1
2
3
if let Some(x) = value && x > 10 {
// ...
}
while let
1
2
3
while let PATTERN = value {
// ...
}
let-else
1
2
3
let PATTERN = value else {
// must diverge
};
Examples:
1
2
3
let Some(value) = option else {
return;
};
1
2
3
let Ok(value) = result else {
return Err(error);
};
57. The Most Important Mental Model
Do not think of:
1
if let Some(x) = value
as strange syntax.
Read it as:
1
2
3
"If value matches the pattern Some(x),
bind the inside value to x,
and execute the block."
Similarly:
1
while let Some(x) = iterator.next()
means:
1
2
3
"Keep calling next().
If it returns Some(x), execute the body.
When it returns None, stop."
And:
1
2
3
let Some(x) = value else {
return;
};
means:
1
2
"Extract x if value is Some.
Otherwise leave this control-flow path."
Once you understand this, all three constructs become variations of the same idea:
1
2
3
4
5
6
7
8
9
10
11
12
13
PATTERN MATCHING
|
+------------+------------+
| | |
match if let while let
| | |
many cases one case repeated case
+
let-else
|
extract or exit
58. Key Takeaways
match,if let,while let, andlet-elseare all based on Rust’s pattern-matching system.matchis exhaustive.if letis useful when you care about one pattern.while letrepeatedly executes code while a pattern matches.let-elseextracts a value and handles failure by leaving the current control-flow path.Some(x)is a pattern, not just a value.Ok(x)andErr(e)are patterns.- Tuple and struct destructuring are patterns.
_matches anything without binding it.|creates or-patterns...ignores remaining fields/elements.iftests boolean conditions;if lettests patterns.- Match guards add additional boolean conditions to patterns.
@lets you both match a pattern and bind the matched value.- Patterns interact directly with ownership and borrowing.
match &valueis often useful when you want to inspect a value without moving it.while let Some(x) = iterator.next()is one of the most important iterator patterns to recognize.let-elseis especially useful for early validation and keeping the successful path unindented.- Understanding patterns is more important than memorizing the individual constructs.
- Once patterns are understood,
match,if let,while let, andlet-elsebecome different ways of applying the same underlying mechanism.