Rust: Rust Flow Control
Last updated: 2026-08-26
Rust's
matchis not aswitch—it's more powerful because the compiler checks to make sure you've covered all cases.
Control flow is the backbone of a program. In addition to traditional if/else, Rust has two major strengths: match pattern matching and if let syntactic sugar.
1. What You'll Learn
- Use
if/else if/elsefor conditional branching - Use
matchfor pattern matching - Exhaustive checking of "match" and wildcards
_ - Use
if letto simplify single-mode matching - Add additional conditions to the "match guard" rule
2. The Story of the Rock-Paper-Scissors Referee
(1) The Struggle: Writing a Rock-Paper-Scissors Decision Logic Using if-else Statements
Emma is a game developer who wants to add a Rock-Paper-Scissors feature to her game:
Player's turn: Rock (1), Scissors (2), Paper (3)
Computer: Pick one at random
Needs to be determined: Win, Lose, or Tie
Code that uses if/else for determination:
if Player == 1 && Computer == 1 → Tie
else if Player == 1 && Computer == 2 → Win
else if Player == 1 && Computer == 3 → Lose
... Still needs 6 branches!
Writing a whole bunch of
if/elsemakes it easy to miss certain combinations, and it's hard to read.
(2) An Elegant Solution for Rust's match Statement
fn main() {
let player = 1; // Rock
let computer = 2; // Scissors
let result = match (player, computer) {
(1, 1) | (2, 2) | (3, 3) => "Tie",
(1, 2) | (2, 3) | (3, 1) => "You Win!",
(1, 3) | (2, 1) | (3, 2) => "You Lose!",
_ => "Invalid Punch",
};
println!("Results: {}", result);
}
matchIt's like a referee here: it maps the player's and the computer's punch combinations one-to-one, with no omissions and no ambiguity. The compiler checks to see if you've written all 9 combinations.
3. if/else Conditional Branching
(1) Basic Usage
fn main() {
let number = 7;
if number < 5 {
println!("A number less than 5");
} else if number == 5 {
println!("The number equals 5");
} else {
println!("The number is greater than 5");
}
}
(2) If the if is an expression—it can return a value
let condition = true;
let value = if condition { 5 } else { 6 };
// Note:The two branches must be of the same type.
println!("value = {}", value); // value = 5
Important: The two branches of
ifmust be of the same type.if true { 5 } else { "six" }will result in a compilation error.
4. match Pattern Matching
(1) Basic Syntax
graph TB
A[match Expressions] --> B[Each branch = A Pattern]
B --> C[Pattern match successful → Run this branch]
B --> D[Pattern Mismatch → Try the next branch]
A --> E[Finally, there must be _ Wildcard Fallback]
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("other"), // Wildcard——Match all remaining cases
}
(2) Key Rules for "match"
| Rule | Description |
|---|---|
| Exhaustiveness | Must cover all possible values; omitting any branch will result in a compilation error |
Wildcard _ |
Matches all remaining cases; placed at the end |
Multi-mode | |
One branch matches multiple patterns: 1 | 2 => |
| Range Matching | 1..=5 Matches the closed interval from 1 to 5 |
| Guard | Additional conditions: x if x > 5 => |
(3) Comparing if and match
| Characteristics | if/else |
match |
|---|---|---|
| Matching Method | Boolean Condition | Pattern Matching |
| Exhaustive Check | None (else can be omitted) | Mandatory (must cover all values) |
| Deconstruction Capabilities | None | Supports deconstruction of enumerations, tuples, and structs |
| Multi-value matching | Requires ` | |
| Use Cases | Range Checks, Boolean Logic | Enumeration Processing, Value Classification, Deconstruction |
5. if let Syntactic Sugar
When you're only interested in a specific pattern and not in other values, if let is more concise than match:
let optional = Some(5);
// match Writing Style (verbose)
match optional {
Some(value) => println!("The value is: {}", value),
_ => (), // You must write this empty branch.
}
// if let Writing Style (Concise)
if let Some(value) = optional {
println!("The value is: {}", value);
// No need to write _ => ()
}
(4) Quick Reference for Control Flow Keywords
| Keyword/Syntax | Purpose | Example |
|---|---|---|
if / else if / else |
Conditional Branch | if x > 0 { ... } else { ... } |
match |
Pattern Matching | match x { 1 => ..., _ => ... } |
if let |
Single-mode matching syntactic sugar | if let Some(v) = x { ... } |
while |
Conditional Loops | while x > 0 { ... } |
while let |
Pattern-Matching Loop | while let Some(v) = iter.next() { ... } |
loop |
Infinite Loop | loop { ... break; } |
| Iterator traversal | ||
break |
Exit Loop | break; or break value; |
continue |
Skip this iteration | continue; |
6. Complete Example
▶ Example 1: if/else Grade Classification (Difficulty ⭐)
Output:
Fractions: 88, Level: <grade>
// ============================================
// Use if expression to grade scores and return a string
// ============================================
fn main() {
let score = 88;
let grade = if score >= 90 {
"Excellent (A)"
} else if score >= 80 {
"Good (B)"
} else if score >= 70 {
"Intermediate (C)"
} else if score >= 60 {
"Passing Grade (D)"
} else {
"Fail (F)"
};
println!("Fractions: {}, Level: {}", score, grade);
}
Output:
Fractions: 88, Level: Good (B)
Output:
Status Code <code>: <handle_status_code(code)>
ifEach branch of the expression must return the same type. Here, all branches return&str, so the compiler is happy.
▶ Example 2: Using match to handle HTTP status codes (Difficulty: ⭐⭐)
Output:
Status Code <code>: <handle_status_code(code)>
// ============================================
// Use match to gracefully handle HTTP Status Codes
// ============================================
fn handle_status_code(code: u16) -> &'static str {
match code {
200 => "OK: Request Successful",
201 => "Created: The resource has been created",
204 => "No Content: No results found",
301 | 302 => "Redirect: The resource has been moved",
400 => "Bad Request: Invalid request format",
401 => "Unauthorized: Unauthorized",
403 => "Forbidden: Access Denied",
404 => "Not Found: Resource does not exist",
500 => "Internal Server Error: Internal Server Error",
502 | 503 => "Server Error: The server is temporarily unavailable",
_ => "Unknown: Unknown status code",
}
}
fn main() {
let codes = [200, 404, 418, 500];
for &code in codes.iter() {
println!("Status Code {}: {}", code, handle_status_code(code));
}
}
Output:
Fractions: 88, Level: Good (B)
Output:
Status Code 200: OK: Request Successful
Status Code 404: Not Found: Resource does not exist
Status Code 418: Unknown: Unknown status code
Status Code 500: Internal Server Error: Internal Server Error
|The operator allows a branch to match multiple patterns, avoiding duplicate code._Wildcards ensure that all status codes are covered.
▶ Example 3: match guards and if let (Difficulty ⭐⭐)
Output:
Very Large Numbers: <x>
Big Numbers: <x>
Small numbers: <x>
No numbers
Language is: <lang>
Not null: <x>
No value
// ============================================
// match Guard + if let Simplified Pattern Matching
// ============================================
fn main() {
let number = Some(42);
// match Guard: Add additional conditions to the pattern
match number {
Some(x) if x > 100 => println!("Very Large Numbers: {}", x),
Some(x) if x > 50 => println!("Big Numbers: {}", x),
Some(x) => println!("Small numbers: {}", x),
None => println!("No numbers"),
}
// if let Syntactic sugar -- Only care about the Some case
let value = Some("Rust");
if let Some(lang) = value {
println!("Language is: {}", lang);
}
// if let with else
let empty: Option<i32> = None;
if let Some(x) = empty {
println!("Not null: {}", x);
} else {
println!("No value");
}
}
Output:
Small numbers: 42
Language is: Rust
No value
Use the
ifkeyword in a match statement to add an additional condition after the pattern—this branch will only be entered if the pattern matches and the condition is true.if letis suitable for scenarios where you are "only concerned with one pattern."
▶ Example 4: Using match to deconstruct enums and tuples (Difficulty: ⭐⭐⭐)
Output:
=== Graphical Description ===
<describe_shape(shape)>
=== Coordinate Classification ===
Point <point>: <desc>
// ============================================
// match Deconstruction: Enumeration + Tuple + Guard
// ============================================
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64, f64),
}
fn describe_shape(shape: &Shape) -> String {
match shape {
Shape::Circle(radius) if *radius > 10.0 => {
format!("Big Circle (Radius {:.1})", radius)
}
Shape::Circle(radius) => {
format!("Small Circle (Radius {:.1})", radius)
}
Shape::Rectangle(w, h) if w == h => {
format!("Square (Side length {:.1})", w)
}
Shape::Rectangle(w, h) => {
format!("Rectangle (Width {:.1}, Height {:.1})", w, h)
}
Shape::Triangle(a, b, c) => {
format!("Triangle (Sides {:.1}, {:.1}, {:.1})", a, b, c)
}
}
}
fn main() {
let shapes = [
Shape::Circle(5.0),
Shape::Circle(15.0),
Shape::Rectangle(4.0, 4.0),
Shape::Rectangle(3.0, 5.0),
Shape::Triangle(3.0, 4.0, 5.0),
];
println!("=== Graphical Description ===");
for shape in &shapes {
println!("{}", describe_shape(shape));
}
println!("\n=== Coordinate Classification ===");
let points: [(i32, i32); 4] = [(0, 0), (3, 0), (0, -2), (5, 7)];
for point in points {
let desc = match point {
(0, 0) => "Origin".to_string(),
(x, 0) => format!("On x-axis (x={})", x),
(0, y) => format!("On y-axis (y={})", y),
(x, y) if x > 0 && y > 0 => format!("First Quadrant ({}, {})", x, y),
(x, y) => format!("Other Quadrants ({}, {})", x, y),
};
println!("Point {:?}: {}", point, desc);
}
}
Output:
=== Graphical Description ===
Small Circle (Radius 5.0)
Big Circle (Radius 15.0)
Square (Side length 4.0)
Rectangle (Width 3.0, Height 5.0)
Triangle (Sides 3.0, 4.0, 5.0)
=== Coordinate Classification ===
Point (0, 0): Origin
Point (3, 0): On x-axis (x=3)
Point (0, -2): On y-axis (y=-2)
Point (5, 7): First Quadrant (5, 7)
The destructuring capabilities of
matchallow you to simultaneously match enum variants, extract inner values, and apply guard conditions to implement precise classification logic. The compiler's exhaustive checking ensures that you won't overlook any cases.
▶ Example 5: Practical Application of if let and while let (Difficulty: ⭐⭐)
Output:
=== if let Processing Option ===
Division Successful: <value>
Division Failed: Divisor is zero
=== if let Chain Processing ===
Alice's number: <num>
Bob's number: <num>
Bob has no data input
=== while let Simulation Iteration ===
Popped: <top>
The stack is empty
// ============================================
// if let and while let: Simplify Option/Result Processing
// ============================================
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
println!("=== if let Processing Option ===");
let results = [divide(10.0, 2.0), divide(8.0, 0.0), divide(15.0, 3.0)];
for result in &results {
if let Some(value) = result {
println!("Division Successful: {:.2}", value);
} else {
println!("Division Failed: Divisor is zero");
}
}
println!("\n=== if let Chain Processing ===");
let alice_input: Option<&str> = Some("42");
let bob_input: Option<&str> = None;
if let Some(num_str) = alice_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Alice's number: {}", num);
}
}
if let Some(num_str) = bob_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Bob's number: {}", num);
}
} else {
println!("Bob has no data input");
}
println!("\n=== while let Simulation Iteration ===");
let mut stack = vec![3, 2, 1];
while let Some(top) = stack.pop() {
println!("Popped: {}", top);
}
println!("The stack is empty");
}
Output:
=== if let Processing Option ===
Division Successful: 5.00
Division Failed: Divisor is zero
Division Successful: 5.00
=== if let Chain Processing ===
Alice's number: 42
Bob has no data input
=== while let Simulation Iteration ===
Popped: 1
Popped: 2
Popped: 3
The stack is empty
if letis suitable for scenarios where you are only concerned withSome/Okand can ignore other cases;while letis suitable for scenarios where you iterate through values until you encounterNone(such aspop()stack operations). Both are more concise than the fullmatch.
❓ FAQ
match and switch?match is much more powerful than switch.match checking so important?if let?match?if expression are of different types?📖 Summary
if/elseis an expression (that can return a value); all branches must be of the same typematchWhen performing pattern matching, the compiler enforces exhaustive checking- The wildcard
_matches all remaining values and is placed at the end of the match if letis syntactic sugar formatch, suitable for scenarios where you're "only concerned with one pattern"- The guard (with the
ifcondition) makes pattern matching more flexible - The
|operator allows a branch to match multiple patterns
📝 Exercises
- Difficulty ⭐: Write a function
fn number_to_word(n: i32) -> &'static strthat usesmatchto map the digits 0–5 to their corresponding English words (zero/one/two/three/four/five) and returns "unknown" for all other digits. - Difficulty ⭐⭐: Define an enum
enum TrafficLight { Red, Yellow, Green }and usematchto return the corresponding "wait time" for each color (red: 30s, yellow: 3s, green: 45s). - Difficulty ⭐⭐⭐ : Write a function
fn describe_point(point: (i32, i32))that usesmatchto process a two-dimensional coordinate point: if the point is at the origin(0,0), output "origin"; if it is on the x-axis(x,0), output "on x-axis"; if it is on the y-axis(0,y), output "on y-axis"; for all other cases, output the coordinate values.