Flow control: what each construct evaluates to, and how far each exit jumps¶
Level: 101 · for newcomers
One line: Rust has two branches (if, match), three loops (for, while, loop) and five ways out (continue, break, break 'label, return, ?). Every one of the first five is an expression with a value and a type, and every way out jumps a fixed distance you can read off the keyword.
This page is the map of the section: one program that runs each construct once, and a folder per construct for the details.
Every construct is an expression¶
let n = 7;
let size = if n < 10 { "small" } else { "large" }; // "small"
let parity = match n % 2 { 0 => "even", _ => "odd" }; // "odd"
let mut tries = 0;
let found = loop { tries += 1; if tries == 3 { break tries * 10; } }; // 30
| Construct | Evaluates to | Lesson |
|---|---|---|
if … else |
the value of the branch that ran; both branches must have the same type | if expressions |
match |
the value of the arm that matched; every arm the same type, and every case covered | match expressions |
for |
always () |
for loops |
while |
always () |
while loops |
loop |
the value handed to break, or ! when nothing breaks |
loop |
'label: { … } |
the value handed to break 'label, or the block's last expression |
Loop labels |
for and while cannot produce a value because they can end without a break: a range can be empty and a condition can be false on the first check, and then there would be no value to hand over. loop has no such exit, so break value is allowed there and in a labelled block, and nowhere else.
The rule under all of this is a block is an expression: a { } has the value of its last line without a semicolon.
Every way out jumps a known distance¶
| Exit | Where control goes | Works in | Carries a value | Lesson |
|---|---|---|---|---|
continue |
the next pass of the innermost loop | for, while, loop |
no | continue |
continue 'label |
the next pass of the labelled loop | for, while, loop |
no | Loop labels |
break |
past the end of the innermost loop | for, while, loop |
only in loop |
break |
break 'label |
past the end of the labelled loop or block | loops and labelled blocks | only in loop and blocks |
Loop labels |
return |
out of the function, through every loop | anywhere in a function body | the function's return value | Functions |
? |
out of the function when the value is Err or None |
a function returning Result or Option |
the error | The ? operator |
A panic and std::process::exit leave too, further still: the first unwinds the thread, the second ends the process. None of these is goto — Rust has no jump to an arbitrary line. Every exit in the table drops the values in scope on the way out, and so does an unwinding panic; process::exit is the exception, and runs no destructors at all.
The verified output¶
Verified output of flow_control.rs — regenerated by tools/run_examples.py, never hand-typed.
1. What each construct evaluates to
if … else &str "small"
match &str "odd"
for () ()
while () ()
loop + break value i32 30
labelled block i32 12
(the for loop added up to 28; the while loop counted to 3)
2. How far each way out jumps
continue next pass kept the evens: [8, 12]
break end of this loop saw before the first r > 6: [3]
break 'rows end of outer loop first a * b == 6: Some((2, 3))
return out of the fn first_even: Some(8)
? out of the fn total(1, 2, 3): Ok(6)
? out of the fn total(1, x, 3): Err("\"x\": invalid digit found in string")
Reading it:
- Section 1, the types. The two branches produced a
&str;forandwhileproduced(), even though thefordid real work (the sum, 28, lives in amutoutside it). Theloopand the labelled block produced ani32throughbreak. - Section 2, the distances.
continuekept looping and skipped the odd readings.breakstopped at 8, the first reading over 6, so only 3 was seen.break 'rowsleft both loops at(2, 3), where a plainbreakwould have left only the inner one and letacarry on to 3 and 4.returnleft the loop andfirst_eventogether, and?lefttotalat"x"without reading"3".
Which one to reach for¶
- Two outcomes of a condition →
if. Several outcomes of one value →match, which also tells you when you forgot one. - You know the sequence (a range, a collection, an iterator) →
for. You know only a condition →while. You know neither →loop, left bybreakorreturn. - You need a value out of a search → an iterator method (
find,position,any) first; thenloopwithbreak value, or a labelled block. - You want the index → usually you don't: loops without an index.
The pattern-driven forms live elsewhere¶
if let, while let and let else are flow control driven by a pattern rather than a bool, and they are taught from the Option side: if let, while let, let else. What rustc turns a for and a ? into — a loop inside matches — is printed on Printing the HIR.
If you are coming from another language¶
- Python. The same five constructs, with three changes.
ifhas a value, soa if c else bbecomesif c { a } else { b }.match(Python 3.10+) is a statement there and is not checked for completeness; here it has a value and a missing case is a compile error. And thefor … else/while … elseclause does not exist — the Rust answer to "did the loop finish without breaking?" is aloopor labelled block that breaks with a value, oriter().find(). Python has no labelledbreak; the flag variable you would write there is whatbreak 'labelreplaces. - C.
for (init; cond; step)has no counterpart: a Rustforwalks an iterator, and a counting loop isfor i in 0..n.switchbecomesmatch, with no fall-through and nobreakbetween arms. There is nodo … while(writeloopwith abreakat the bottom) and nogoto; the jump-to-cleanup idiom is replaced byDrop, which runs on every exit in the table. And a condition must be abool:while non an integer isE0308, where C reads it asn != 0. - ABAP.
IF … ELSEIF … ELSE … ENDIFandCASE … WHEN … WHEN OTHERS … ENDCASEare statements, so the closer match for Rust'sifandmatchis the constructor expressionsCOND #( WHEN … THEN … ELSE … )andSWITCH #( … ), which have values.DO n TIMESisfor _ in 0..n,WHILE … ENDWHILEiswhile, andDO … ENDDOwithEXITisloopwithbreak.EXITandCONTINUEleave or skip the innermost loop only, as in Rust; what Rust adds is the label to name an outer one.CHECK condinside a loop isif !cond { continue; }. The loop countersy-indexhas no counterpart — ask for it with.enumerate().
Reading Rust in Action §2.4¶
Chapter 2 of Rust in Action has a section called "Flow control". Each of its subsections has a folder here, and its claims, run checks what it says against rustc 1.98.0.
| Book section | Lessons here |
|---|---|
| 2.4.1 For: the central pillar of iteration | for loops · Loops without an index · iter, iter_mut, into_iter |
| 2.4.2 Continue | continue |
| 2.4.3 While | while loops · Time and benchmarking for listing 2.7 |
| 2.4.4 Loop | loop |
| 2.4.5 Break | break · Loop labels |
| 2.4.6 If, if else, and else | if expressions |
| 2.4.7 Match | match expressions · Pattern matching |
See also¶
- The book's flow-control claims, run — twenty-one claims from §2.4, and the five that need a correction
- A block is an expression — the rule every value above comes from
- The never type
!— the type ofloop {},return,breakandcontinuethemselves - The unit type
()— whatforandwhileevaluate to - Iterators — where most
forloops go once the collection is real - Loop expressions ↗ ·
ifandif letexpressions ↗ ·matchexpressions ↗ ·returnexpressions ↗
Sources¶
- The Rust Reference, chapter 8.2 "Expressions": loop,
if,matchandreturnexpressions (links above) - Tim McNamara, Rust in Action (Manning, 2021), §2.4 "Flow control"
- The Rust Programming Language, §3.5 "Control Flow" ↗
Po polsku¶
W Ruscie jest pięć konstrukcji sterujących przepływem — dwa rozgałęzienia (if, match) i trzy pętle (for, while, loop) — i każda z nich jest wyrażeniem, czyli ma wartość i typ. if i match dają wartość gałęzi, która się wykonała. for i while zawsze dają (), bo mogą się skończyć bez break: zakres bywa pusty, a warunek fałszywy już przy pierwszym sprawdzeniu, i wtedy nie byłoby czego zwrócić. loop takiego wyjścia nie ma, dlatego tylko w nim (i w bloku z etykietą) wolno napisać break wartość.
Wyjść z konstrukcji jest pięć i każde skacze na znaną odległość: continue — do następnego obiegu najbliższej pętli; break — za koniec najbliższej pętli; break 'etykieta — za koniec pętli lub bloku oznaczonego etykietą; return — z całej funkcji, przez wszystkie pętle naraz; ? — z funkcji, gdy wartość to Err albo None. Instrukcji goto nie ma wcale, a sprzątanie, do którego w C służył skok do etykiety, robi Drop, wywoływany przy każdym z tych wyjść. Programistę ABAP-a ucieszy, że EXIT i CONTINUE działają tak samo — dotyczą tylko najbliższej pętli — a Rust dokłada do nich etykietę, która pozwala wskazać pętlę zewnętrzną.
Szukaj po polsku: sterowanie przepływem · instrukcje sterujące · wyrażenie a instrukcja · wyjście z pętli zagnieżdżonej · rust control flow expressions · rust break continue return