Skip to content

05 — Control flow

Mighty has if, match, for, while, and loop. All but loop are also expressions; they may produce values.

Match — from chapter 02

fn classify(n: I32) -> Str {
  match n {
    0 => "zero"
    1..10 => "small"
    _ => "big"
  }
}
  • 0 => matches the integer literal.
  • 1..10 is a half-open range pattern (1 through 9). The inclusive form is 1..=10.
  • _ is the wildcard. Match is exhaustive; the wildcard makes that trivially true.

Loops

fn process(items: &[I32]) {
  for item in items {
    work(item)?
  }
  while ready() {
    step()
  }
  loop {
    tick()
  }
}
  • for item in items iterates a borrowed slice. The loop variable is a borrow because items is &[I32].
  • ? inside a loop body propagates the error out of the enclosing function, just as in straight-line code.
  • while runs as long as the condition is true.
  • loop runs forever, exited via break, return, or a panic.

break and continue

fn first_match(xs: &[I32], needle: I32) -> Option[USize] {
  for i in 0..xs.len() {
    if xs[i] == needle { return Some(i) }
  }
  None
}

fn retry_until_ok() -> Result[Str, Err] {
  let mut tries = 0
  loop {
    let r = fetch()
    if r.is_ok() { break r }              // break carries the value out
    tries = tries + 1
    if tries >= 5 { break Err("giveup") }
  }
}

fn sum_evens(n: I32) -> I32 {
  let mut total = 0
  for i in 0..n {
    if i % 2 == 1 { continue }            // skip odd, re-enter the loop
    total = total + i
  }
  total
}
  • break unwinds to the nearest enclosing loop. break <value> makes the loop expression evaluate to <value> (only loop { … } exposes a value today; while and for always evaluate to Unit).
  • continue re-enters the loop header without running the rest of the body.
  • Labels ('outer: loop { break 'outer }) are not yet in the shipped grammar — break always exits the innermost loop. Tracked by amendment A82 (DEFER-V1.1).

while let — pattern-driven loops

A while let loop iterates as long as the scrutinee keeps matching a pattern. It's the canonical shape for draining anything that yields Option[T] until exhaustion — the most common case being a stream pulled one chunk at a time:

fn drain(stream: MessageStream) {
  while let Some(chunk) = stream.next() {
    handle(chunk)
  }
}

The scrutinee is re-evaluated every iteration; the bindings introduced by the pattern (chunk above) are in scope only inside the body. The loop exits the moment the pattern fails — for Option[T], that's the first None. break and continue work exactly as in plain while. There is no else branch (unlike if let); use a follow-up statement if you need post-loop fallback work.

There is no defer. Deterministic cleanup is handled by ownership and destructors. See spec §11.4.

Try it

mty check examples/06_for_while_loop.mty

Next

Continue to 06 — Agents and protocols.