Rust control flow
last modified October 25, 2026
In this article we show how to control the flow of a Rust program.
The statements of a Rust program are executed from top to bottom. The flow of
the program can be altered with conditions and loops: if,
else, loop, while, for, and
match. The break and continue keywords
change the flow inside loops.
Most of these constructs are expressions in Rust; they can produce a value that we assign to a variable.
The examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q. For generating random values, the
first two examples use the rand crate.
[dependencies] rand = "0.10.3"
We add the dependency with cargo add rand.
The if condition
The if keyword creates a conditional test. It can be followed by
else if and else branches. The condition must be a
bool; unlike in C, an integer is not accepted. The condition is
not placed in parentheses, but the braces around the blocks are required.
fn main() {
let n = rand::random_range(-5..5);
if n < 0 {
println!("{n} is negative");
} else if n == 0 {
println!("{n} is zero");
} else {
println!("{n} is positive");
}
}
We randomly pick a value from the range -5..5. Based on the value, we print a message. There are three branches that can be executed.
let n = rand::random_range(-5..5);
The random_range function returns a random value from the given
range. The range -5..5 includes -5 but excludes 5.
if n < 0 {
println!("{n} is negative");
} else if n == 0 {
If the condition after if is true, its block is executed and the
other branches are skipped. Otherwise, the else if condition is
tested.
} else {
println!("{n} is positive");
}
The else block is executed when none of the previous conditions is
true.
$ cargo run -q 2 is positive $ cargo run -q 1 is positive $ cargo run -q -3 is negative
The if expression
The if is an expression; it can return a value. Rust has no
ternary operator, the if expression is used instead.
fn main() {
let n = rand::random_range(-5..5);
let msg = if n < 0 {
"negative"
} else if n == 0 {
"zero"
} else {
"positive"
};
println!("{n} is {msg}");
}
The if expression returns a string slice, which we assign to the
msg variable.
let msg = if n < 0 {
"negative"
} else if n == 0 {
The value of each branch is the last expression of its block, written without
a semicolon. All branches must return the same type. The whole
let statement ends with a semicolon after the closing brace.
$ cargo run -q -4 is negative $ cargo run -q 2 is positive $ cargo run -q 4 is positive
The loop keyword
An infinite loop is created with the loop keyword. The loop is
terminated with the break keyword.
fn main() {
let mut i = 0;
loop {
println!("falcon");
i += 1;
if i == 5 {
break;
}
}
}
In the example, we print a word five times.
let mut i = 0;
We define a counter. It must be mutable, since we change it in the loop.
i += 1;
if i == 5 {
break;
}
In each iteration, we increase the counter. When it reaches 5, the loop is
terminated with break.
$ cargo run -q falcon falcon falcon falcon falcon
Returning a value from loop
A loop is an expression too. The value given to
break becomes the value of the loop.
fn main() {
let mut a = 0u64;
let mut b = 1u64;
let first_big = loop {
(a, b) = (b, a + b);
if a > 1000 {
break a;
}
};
println!("The first Fibonacci number above 1000 is {first_big}");
}
The program generates Fibonacci numbers until it finds the first one greater than 1000.
(a, b) = (b, a + b);
With a destructuring assignment, we move to the next pair of Fibonacci numbers in one step.
if a > 1000 {
break a;
}
The break a statement ends the loop and makes a the
value of the whole loop expression, which is assigned to
first_big.
$ cargo run -q The first Fibonacci number above 1000 is 1597
The while loop
The while loop executes its block as long as the condition is
true. The condition is checked before each iteration.
fn main() {
let mut x = 1;
while x <= 10 {
print!("{x} ");
x += 1;
}
println!();
println!("x: {x}");
}
In the program, we print the numbers 1 through 10. In the end, we print the
value of the x counter.
while x <= 10 {
print!("{x} ");
x += 1;
}
The block is executed while x is less than or equal to 10. When
x becomes 11, the condition is false and the loop ends; that is
why the last line shows 11.
$ cargo run -q 1 2 3 4 5 6 7 8 9 10 x: 11
The while loop is useful when we do not know the number of
iterations in advance. In the next example, we compute the Collatz sequence.
fn main() {
let mut n = 27;
let mut steps = 0;
while n != 1 {
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
steps += 1;
}
println!("27 reaches 1 after {steps} steps");
}
If the number is even, we divide it by two; otherwise, we multiply it by three and add one. We repeat this until the number reaches 1 and count the steps.
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
The new value of n is computed with an if
expression.
$ cargo run -q 27 reaches 1 after 111 steps
The for loop
The for loop goes over the values of an iterator, such as a range
or a collection. It is the most common loop in Rust.
fn main() {
for i in 1..=10 {
print!("{i} ");
}
println!();
for i in (0..20).step_by(5) {
print!("{i} ");
}
println!();
for i in (1..=5).rev() {
print!("{i} ");
}
println!();
let words = ["sky", "cup", "forest"];
for (i, word) in words.iter().enumerate() {
println!("{i}: {word}");
}
}
In the program, we iterate over ranges and an array.
for i in 1..=10 {
print!("{i} ");
}
The inclusive range 1..=10 includes 10; the range
1..10 would stop at 9. In each iteration, the i
variable holds the current value.
for i in (0..20).step_by(5) {
The step_by method takes every fifth value of the range.
for i in (1..=5).rev() {
With rev, we count down from 5 to 1.
for (i, word) in words.iter().enumerate() {
println!("{i}: {word}");
}
The enumerate method gives us the index together with each
element of the array.
$ cargo run -q 1 2 3 4 5 6 7 8 9 10 0 5 10 15 5 4 3 2 1 0: sky 1: cup 2: forest
The continue keyword
The continue keyword skips the rest of the current iteration and
starts the next one.
fn main() {
for i in 1..=20 {
if i % 3 != 0 {
continue;
}
print!("{i} ");
}
println!();
}
We print the numbers from 1 to 20 that are divisible by three. For other
numbers, continue skips the print! call.
$ cargo run -q 3 6 9 12 15 18
Loop labels
By default, break and continue apply to the innermost
loop. With a label, we can break out of an outer loop. A label starts with a
single quote.
fn main() {
let grid = [[1, 4, 7], [2, 9, 3], [8, 5, 6]];
'rows: for (r, row) in grid.iter().enumerate() {
for (c, &val) in row.iter().enumerate() {
if val == 9 {
println!("found 9 at row {r}, column {c}");
break 'rows;
}
}
println!("row {r} has no 9");
}
}
We search a two-dimensional array for the value 9.
'rows: for (r, row) in grid.iter().enumerate() {
The outer loop is labelled 'rows.
break 'rows;
When the value is found, we end both loops. A plain break would
end only the inner loop, and the outer loop would continue with the third row.
$ cargo run -q row 0 has no 9 found 9 at row 1, column 1
The match expression
The match expression compares a value against a series of
patterns and executes the code of the first matching arm. The
patterns must cover all possible values. It is covered in detail in the
Rust match expression article.
fn main() {
let grades = ["A", "B", "C", "D", "E", "FX", "Z"];
for grade in grades {
let res = match grade {
"A" | "B" | "C" | "D" | "E" => "passed",
"FX" => "failed",
_ => "unknown grade",
};
println!("{grade}: {res}");
}
}
We go over an array of grades and print whether each grade is passing.
"A" | "B" | "C" | "D" | "E" => "passed",
With the | operator, one arm matches several values.
_ => "unknown grade",
The _ wildcard matches any value. Since a string can hold any
text, the compiler requires this arm to make the match exhaustive.
$ cargo run -q A: passed B: passed C: passed D: passed E: passed FX: failed Z: unknown grade
The if let expression
The if let expression runs a block only when a value matches a
pattern. It is a shorter form of a match with a single interesting
arm.
fn main() {
let words = ["sky", "forest", "cup"];
if let Some(pos) = words.iter().position(|&w| w == "forest") {
println!("forest is at index {pos}");
}
if let Some(pos) = words.iter().position(|&w| w == "pen") {
println!("pen is at index {pos}");
} else {
println!("pen not found");
}
}
The position method returns an Option with the index
of the searched element.
if let Some(pos) = words.iter().position(|&w| w == "forest") {
println!("forest is at index {pos}");
}
If the result is Some, the index is bound to pos and
the block is executed. If it is None, nothing happens.
} else {
println!("pen not found");
}
An if let can have an else branch for the values that
do not match the pattern.
$ cargo run -q forest is at index 1 pen not found
The while let loop
The while let loop runs as long as a value matches a pattern.
fn main() {
let mut stack = vec![1, 2, 3, 4];
while let Some(top) = stack.pop() {
print!("{top} ");
}
println!();
println!("stack is empty: {}", stack.is_empty());
}
We use a vector as a stack and take its elements from the top.
while let Some(top) = stack.pop() {
print!("{top} ");
}
The pop method removes the last element and returns it in
Some. When the vector is empty, it returns None and
the loop ends.
$ cargo run -q 4 3 2 1 stack is empty: true
The let else statement
The let else statement binds a variable if the value matches the
pattern. If it does not, the else block is executed; this block
must leave the current scope, for instance with return,
break, or a panic.
fn parse_age(input: &str) -> Option<u32> {
let Ok(age) = input.trim().parse::<u32>() else {
println!("'{input}' is not a valid age");
return None;
};
Some(age)
}
fn main() {
println!("{:?}", parse_age("34"));
println!("{:?}", parse_age("old"));
}
The parse_age function converts a string into an age.
let Ok(age) = input.trim().parse::<u32>() else {
println!("'{input}' is not a valid age");
return None;
};
If parsing succeeds, the value is bound to age, which is
available in the rest of the function. Otherwise, we print a message and return
early. This keeps the main path of the function free of nested blocks.
$ cargo run -q Some(34) 'old' is not a valid age None
Source
The Rust Programming Language: Control Flow
The Rust Reference: Loops and other breakable expressions
In this article we have worked with control flow structures in Rust.
Author
List all Rust tutorials.