loop
The loop keyword creates an unconditional infinite loop that executes its block of code repeatedly until the program explicitly exits the loop (using break or return) or terminates (via panic!, std::process::exit, or an external signal).
Basic Syntax and Execution Flow
A loop expression consists of the loop keyword followed by a brace-enclosed block { ... }:
fn main() {
let mut counter = 1;
loop {
println!("Iteration {counter}");
if counter == 3 {
break;
}
counter += 1;
}
println!("Exited loop after {counter} iterations.");
}
Iteration 1
Iteration 2
Iteration 3
Exited loop after 3 iterations.
Unlike while or for, loop has no header condition. At the end of each pass through the body, execution jumps directly back to the top of the block:
+--------------------------+
| |
v |
+-------------------+ |
| Enter `loop` body | |
+---------+---------+ |
| |
v |
+-------------------+ No |
| `break` reached? +----------------+
+---------+---------+
| Yes
v
+-------------------+
| Continue after |
| closing `}` |
+-------------------+
Controlling Iteration Inside loop
Because loop does not test a condition automatically, control flow inside the body relies on explicit statements:
| Keyword | Effect Inside loop |
|---|---|
break |
Immediately terminates the enclosing loop and transfers control to the statement after } |
break value |
Terminates the enclosing loop and makes the loop expression evaluate to value |
continue |
Skips the remainder of the current iteration and jumps directly to the start of the next iteration |
return / return value |
Exits the entire enclosing function immediately, regardless of how deeply nested the loop is |
fn main() {
let mut n = 0;
loop {
n += 1;
if n % 2 == 0 {
continue; // Skip even numbers
}
if n > 5 {
break; // Stop once n exceeds 5
}
println!("Odd number: {n}");
}
}
Odd number: 1
Odd number: 3
Odd number: 5
Why Rust Has loop Instead of while true
In many languages, an unconditional loop is written while (true) { ... }. While while true is syntactically valid in Rust, the compiler's static control-flow analyzer treats loop and while true fundamentally differently.
For while <condition>, the compiler assumes that the condition could evaluate to false on the very first check (without inspecting whether the condition is the literal true). For loop, the compiler knows with certainty that the loop body is guaranteed to execute and that control can only leave the loop via an explicit break, return, or panic.
This guarantee enables two capabilities that while true cannot provide:
1. Definite Variable Initialization
If a variable is declared before a loop and assigned a value before break, the compiler knows the variable is always initialized after the loop:
fn main() {
let result: i32;
loop {
result = 42;
break;
}
println!("result = {result}");
}
result = 42
Replacing loop with while true in the exact same program fails to compile because the compiler assumes a while loop might execute zero times, leaving result uninitialized:
// ❌ does not compile
fn main() {
let result: i32;
while true {
result = 42;
break;
}
println!("result = {result}");
}
warning: denote infinite loops with `loop { ... }`
--> src/main.rs:4:5
|
4 | while true {
| ^^^^^^^^^^ help: use `loop`
|
= note: `#[warn(while_true)]` on by default
error[E0381]: used binding `result` isn't initialized
--> src/main.rs:9:24
|
2 | let result: i32;
| ------ binding declared here but left uninitialized
3 |
4 | while true {
| ---- if this `while` condition is `false`, `result` is not initialized
...
9 | println!("result = {result}");
| ^^^^^^^^ `result` used here but it isn't initialized
error: aborting due to 1 previous error; 1 warning emitted
Note that rustc also includes a built-in lint (#[warn(while_true)]) that explicitly advises replacing while true with loop.
2. Diverging Loops and the Never Type (!)
A loop that contains no break statements can never finish normally. Its type is the never type (!), which coerces into any type required by the surrounding context.
This allows an infinite event loop or server loop—or a loop that only exits via return—to serve as the body of a function returning any type:
fn find_first_multiple(factor: u32, threshold: u32) -> u32 {
let mut current = factor;
// Even though the function returns `u32` and there is no tail expression
// after the loop, `rustc` knows this `loop` never falls through!
loop {
if current > threshold {
return current;
}
current += factor;
}
}
fn main() {
println!("First multiple of 7 above 50: {}", find_first_multiple(7, 50));
}
First multiple of 7 above 50: 56
Variable Scope Across Iterations
The braces { ... } of a loop form a lexical block scope that starts fresh on every iteration and ends when the iteration completes (or when continue or break is executed).
- A variable declared inside the
loopbody is created anew on each iteration and dropped at the end of that iteration. - A variable whose state must persist across iterations must be declared outside (before) the
loop.
fn main() {
let mut outer_total = 0;
loop {
// `per_iteration` is created fresh on every pass through the loop
let per_iteration = 10;
outer_total += per_iteration;
if outer_total >= 30 {
break;
}
}
println!("outer_total = {outer_total}");
}
outer_total = 30
Unreachable Code Detection
Because rustc tracks that a loop without a break never terminates, any statement placed after an unbroken loop is flagged by the unreachable_code warning:
fn main() {
let mut ticks = 0;
loop {
ticks += 1;
if ticks == 2 {
return; // Exits `main` directly; never breaks out of the loop
}
}
println!("This line can never execute");
}
warning: unreachable statement
--> src/main.rs:11:5
|
4 | / loop {
5 | | ticks += 1;
6 | | if ticks == 2 {
7 | | return;
8 | | }
9 | | }
| |_____- any code following this expression is unreachable
10 |
11 | println!("This line can never execute");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable statement
|
= note: `#[warn(unreachable_code)]` on by default