Control Flow
<!-- verified-against: seed-llvm; examples: docs-examples-test -->
Conditions and loops
fn classify(value: i64, values: [3]i64) -> i64 {
let label = if value > 0 { 1 } else if value < 0 { -1 } else { 0 }
let mut index: usize = 0
let mut total = label
while index < len(values) {
total += values[index]
index = index + 1
}
for item in values {
total += item
}
total
}
if is an expression. while and for support break and continue.
A loop headed by name: can be targeted from nested loops with
break :name / continue :name; plain break/continue keep meaning
the innermost loop, and sequential reuse of a label is allowed. Exiting
runs edge-specific cleanup for every scope in between, and never crosses
a spawn boundary. Labeled break/continue take no value.
for over ranges, arrays, slices, and text/buffer values (str,
string, bytes, shared_bytes) lowers to a direct allocation-free
index/length loop. Text and buffers yield u8 elements (byte units,
matching indexing); move-only elements require explicit indexing and
ownership handling. enumerate follows the same types with a usize index.
Match and ADTs
enum Response {
Ok(i64)
Err(str)
}
fn status(value: Response) -> i64 {
match value {
Ok(code) => code
Err(message) => -1
}
}
Match arms need no commas unless the boundary is ambiguous: an arm whose body
ends in an expression followed by a (/[ pattern requires the comma
(B044). Match must be exhaustive unless remaining flow is unreachable.
Patterns support literals, tuples, structs, variants, bindings,
discard, and guards. Move-only payloads transfer only when an arm is selected;
a failed pattern or guard preserves the owner for later arms.
if let and while let
fn label(found: ?string) -> i64 {
if let Some(text) = found { len(text) as i64 } else { 0 }
}
fn count_down(from: ?i64) -> i64 {
let mut total: i64 = 0
let mut current = from
while let Some(value) = current {
total += value
if value > 0 { current = Some(value - 1) } else { current = None }
}
total
}
Both are surface forms over match and desugar to a two-arm match whose second
arm is the complement, so binding, move/borrow behavior, exhaustiveness, and
cleanup are exactly the match rules. if let Some(...) names its complement
None; another variant pattern keeps a wildcard arm, which H032 rejects when
the scrutinee carries a move-only payload (write the explicit match arms
there), and an irrefutable pattern (while let name = value) is rejected as
H029.
Options, results, and ?
fn find(enabled: bool) -> ?i64 {
if enabled { Some(42) } else { None }
}
fn forward(enabled: bool) -> ?i64 {
let value = find(enabled)?
Some(value)
}
? yields the present/success payload or returns the corresponding failure.
Result propagation may change success type but preserves error type. No
implicit allocation or clone occurs. ? cannot cross a spawn boundary.
Cleanup on flow edges
Fallthrough, return, break, and continue execute the verified cleanup plan
for live owners. Panic/trap/abort edges never unwind and run no cleanup.