Skip to content

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; for and while produced (), even though the for did real work (the sum, 28, lives in a mut outside it). The loop and the labelled block produced an i32 through break.
  • Section 2, the distances. continue kept looping and skipped the odd readings. break stopped at 8, the first reading over 6, so only 3 was seen. break 'rows left both loops at (2, 3), where a plain break would have left only the inner one and let a carry on to 3 and 4. return left the loop and first_even together, and ? left total at "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 by break or return.
  • You need a value out of a search → an iterator method (find, position, any) first; then loop with break 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. if has a value, so a if c else b becomes if 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 the for … else / while … else clause does not exist — the Rust answer to "did the loop finish without breaking?" is a loop or labelled block that breaks with a value, or iter().find(). Python has no labelled break; the flag variable you would write there is what break 'label replaces.
  • C. for (init; cond; step) has no counterpart: a Rust for walks an iterator, and a counting loop is for i in 0..n. switch becomes match, with no fall-through and no break between arms. There is no do … while (write loop with a break at the bottom) and no goto; the jump-to-cleanup idiom is replaced by Drop, which runs on every exit in the table. And a condition must be a bool: while n on an integer is E0308, where C reads it as n != 0.
  • ABAP. IF … ELSEIF … ELSE … ENDIF and CASE … WHEN … WHEN OTHERS … ENDCASE are statements, so the closer match for Rust's if and match is the constructor expressions COND #( WHEN … THEN … ELSE … ) and SWITCH #( … ), which have values. DO n TIMES is for _ in 0..n, WHILE … ENDWHILE is while, and DO … ENDDO with EXIT is loop with break. EXIT and CONTINUE leave or skip the innermost loop only, as in Rust; what Rust adds is the label to name an outer one. CHECK cond inside a loop is if !cond { continue; }. The loop counter sy-index has 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

Sources

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