16 Control flow
Control flow determines the order of expression evaluation and how execution transfers between expressions.
Ori is expression-based: all control flow constructs are expressions that produce values. The distinction between “statement” and “expression” is positional — any expression terminated by ; is used as a statement (its value is discarded).
16.0 Statements and expressions
16.0.1 Expression statements
An expression statement is an expression evaluated for its side effects. The result value is discarded. An expression statement is terminated by ; within a block.
print(msg: "hello"); // evaluated for side effect, result discarded
16.0.2 Result expressions
The last expression in a block, if not terminated by ;, is the result expression. Its value becomes the value of the block. See 7.8.1.
16.0.3 Statement-only constructs
The following constructs always have type void and are used only as statements:
letbindingsuseimports- Assignments (
x = v) - Compound assignments (
x += v)
16.0.4 Value-producing expressions
All other constructs are expressions that produce values:
| Expression | Type |
|---|---|
if c then a else b | Unified type of a and b |
if c then a | void (or Never if a is Never) |
match expr { arms } | Unified type of all arm bodies |
for x in s yield e | [T] where T is type of e |
for x in s do e | void |
while c do e | void |
loop { ... break v } | Type of v |
loop { ... break } | void |
loop { ... } (no break) | Never |
try { ... } | Result<T, E> |
16.1 Sequential flow
Expressions in a block { } evaluate top to bottom. Each expression completes before the next begins.
{
let x = 1;
let y = 2;
x + y
}
If any expression terminates early (via break, continue, ?, or panic), subsequent expressions are not evaluated.
16.2 Loop control
16.2.1 Break
break exits the innermost enclosing loop.
loop {
if done then break;
process()
}
break may include a value. The loop expression evaluates to this value:
let result = loop {
let x = compute();
if x > 100 then break x
}
// result = first x greater than 100
A break without a value in a context requiring a value is an error.
16.2.2 Continue
continue skips to the next iteration of the innermost enclosing loop.
for x in items do {
if x < 0 then continue;
process(x);
}
In for...yield, continue without a value skips the element:
for x in items yield {
if x < 0 then continue; // element not added
x * 2
}
continue with a value uses that value for the current iteration:
for x in items yield {
if x < 0 then continue 0; // use 0 instead
x * 2
}
16.2.3 Continue in loop
In loop { }, continue skips to the next iteration. continue value is an error (E0861) — loops do not accumulate values:
loop {
if skip then continue; // OK: start next iteration
if bad then continue 42; // error E0861: loop doesn't collect
process()
}
16.2.4 While loop
A while expression evaluates a condition before each iteration. If the condition is false, the loop exits.
Grammar: See Annex A §
while_expr
while condition do body desugars to:
loop {
if !condition then break;
body
}
The condition expression shall have type bool. The while expression has type void.
while self.pos < self.buf.len() do {
self.pos += 1
}
break exits the loop. break value is a compile-time error (E0860) — while...do does not produce a value.
continue skips to the next iteration (re-evaluating the condition). continue value is a compile-time error (E0861) — while does not accumulate values.
Labels work on while loops as on loop and for:
while:outer scanning do {
while self.pos < self.buf.len() do {
if done then break:outer
}
}
NOTE There is no while...yield form. Use for...yield with an iterator for collection building.
16.3 Labeled loops
Labels allow break and continue to target an outer loop.
16.3.1 Label declaration
Labels use the syntax loop:name, while:name, or for:name, with no space around the colon:
loop:outer {
for x in items do
if x == target then break:outer
}
for:outer x in items do
for y in other do
if done(x, y) then break:outer
16.3.2 Label reference
Reference labels with break:name or continue:name:
loop:search {
for x in items do
if found(x) then break:search x
}
With value:
let result = loop:outer {
for x in items do
if match(x) then break:outer x
None
}
16.3.3 Label scope
A label is visible within the loop body it labels. Labels scope correctly through arbitrary nesting:
loop:a {
loop:b {
loop:c {
break:a; // OK: exits outermost
break:b; // OK: exits middle
break:c // OK: exits innermost
}
}
}
There is no language-imposed limit on label nesting depth.
16.3.4 No label shadowing
Labels cannot be shadowed within their scope:
loop:outer {
loop:outer { // ERROR E0871: label 'outer' already in scope
...
}
}
16.3.5 Type consistency
All break paths for a labeled loop shall produce values of the same type:
let x: int = loop:outer {
for item in items do {
if a(item) then break:outer 1; // int
if b(item) then break:outer "two"; // ERROR E0872: expected int, found str
}
0
}
16.3.6 Continue with value
In for...yield context, continue:name value contributes value to the outer loop’s collection:
let results = for:outer x in xs yield
for:inner y in ys yield {
if special(x, y) then continue:outer x * y; // Contribute to outer
transform(x, y)
}
The value in continue:label value shall have the same type as the target loop’s yield element type.
When continue:label value exits an inner for...yield to contribute to an outer for...yield, the inner loop’s partially-built collection is discarded. Only value is contributed to the outer loop for this iteration.
In for...do context, continue:name value is an error — there is no collection to contribute to:
for:outer x in xs do
for y in ys do {
if skip(x, y) then continue:outer 42; // ERROR E0873: for-do doesn't collect
process(x, y);
}
16.3.7 Valid label names
Labels follow identifier rules. They cannot be keywords:
loop:search { } // OK
loop:_private { } // OK
loop:loop123 { } // OK
loop:for { } // ERROR: 'for' is a keyword
16.4 Error propagation
The ? operator propagates errors and absent values.
16.4.1 On Result
If the value is Err(e), the enclosing function returns Err(e):
@load (path: str) -> Result<Data, Error> = {
let content = read_file(path)?; // Err propagates
let data = parse(content)?;
Ok(data)
}
16.4.2 On Option
If the value is None, the enclosing function returns None:
@find (id: int) -> Option<User> = {
let record = db.lookup(id)?; // None propagates
Some(User { ...record })
}
The function’s return type shall be compatible with the propagated type.
16.5 Terminating expressions
A terminating expression is an expression whose evaluation is guaranteed to not complete normally. Terminating expressions have type Never, which is compatible with any type (see 8.1.1).
The following are terminating expressions:
panic(msg:),todo(),unreachable()— always terminate the programbreakandbreak value— exit the enclosing loopcontinueandcontinue value— skip to the next iterationexpr?when the Err/None branch is taken — returns from the enclosing function- A block
{ ... e }where the last expressioneis terminating if c then t else ewhere bothtandeare terminatingmatch expr { arms }where every arm body is terminatingloop { body }with nobreak— an infinite loop with typeNever
let x: int = if condition then 42 else panic(msg: "unreachable");
// panic(...) has type Never, compatible with int
Code following a terminating expression within the same block is unreachable. The compiler should warn about unreachable code.
{
panic(msg: "fail");
let x = 42; // warning: unreachable code
}
16.6 Conditional evaluation
16.6.1 If-then-else
The condition expression shall have type bool. Only the taken branch is evaluated.
With else: both branches shall have compatible types. The type of the if expression is the unified type of the two branches.
Without else: the then-branch shall have type void or Never. An if without else has type void.
// With else — expression producing a value
let x = if a > b then a else b;
// Without else — statement (void)
if debug then print(msg: "debug info");
// Chained else if
if x > 0 then "positive"
else if x < 0 then "negative"
else "zero"
NOTE There is no if let syntax. Use match for destructuring conditionals.
16.6.2 Match
The scrutinee expression is evaluated exactly once. Arms are tested top-to-bottom. The body of the first matching arm is evaluated; no further arms are tested.
All arm bodies shall have compatible types. The type of the match expression is the unified type of all arm bodies. An arm with type Never is compatible with any other arm type.
The compiler shall verify that the match is exhaustive: every possible value of the scrutinee type is covered by at least one arm. A non-exhaustive match is a compile-time error.
Guards (if) are evaluated after the pattern matches. Guarded arms do not contribute to exhaustiveness; a catch-all pattern (_ or binding) is required after guarded arms.
Unreachable arms (patterns that are subsets of earlier patterns) produce a compiler warning.
match value {
Some(x) if x > 0 -> x, // guard: only positive
Some(x) -> -x, // remaining Some values
None -> 0, // exhaustive with this arm
}
16.6.3 Try blocks
A try block wraps an expression in error-handling context. The ? operator inside a try block propagates to the try boundary rather than the enclosing function.
let result: Result<int, Error> = try {
let $a = parse(input)?;
let $b = validate($a)?;
$a + $b
};
The type of a try block is Result<T, E> where T is the block’s value type and E is the error type from ? operations.
break and continue inside a try block target the enclosing loop (passing through the try boundary).
16.7 Short-circuit operators
Logical operators may skip evaluation of the right operand:
| Operator | Skips right when |
|---|---|
&& | Left is false |
|| | Left is true |
?? | Left is not None/Err |
valid && expensive(); // expensive() skipped if valid is false
cached ?? compute(); // compute() skipped if cached is Some/Ok
16.8 Iteration protocol
A for expression desugars to calls on the Iterable and Iterator traits.
for x in source do body desugars to:
- Call
source.iter()to obtain an iterator (viaIterabletrait) - Call
iterator.next()to obtain(Option<Item>, Iterator) - If
Some(value): bindx = value, evaluate body, go to step 2 - If
None: stop
For for x in source if guard do body, the guard is evaluated after x is bound. If the guard evaluates to false, the iteration skips the body (implicit continue).
For for x in source yield expr:
- Each iteration appends the value of
exprto an accumulating list - The result type is
[T]whereTis the type ofexpr - An empty source produces an empty list
[] breakstops iteration and returns the accumulated values so farbreak valueappendsvalueand returnscontinueskips this element (nothing appended)continue valueappendsvalueinstead of the normal yield expression
Nested for...yield composes as flat-map:
for x in xs
for y in ys
yield (x, y)
// Equivalent to: xs.flat_map(x -> ys.map(y -> (x, y)))
16.8.1 For producing maps
When a for...yield expression yields tuples of (K, V) and the target type is {K: V}, the result is a map:
let m: {str: int} = for item in items yield (item.name, item.count);
16.9 Break and continue summary
| Form | Valid in | Effect |
|---|---|---|
break | loop, while...do, for...do, for...yield | Exit loop |
break value | loop, for...yield | Exit with value |
break:label | Labeled loop, while, for | Exit labeled loop |
break:label value | Labeled loop, for...yield | Exit labeled with value |
continue | loop, while...do, for...do, for...yield | Next iteration |
continue value | for...yield | Substitute value |
continue:label | Labeled loop, while, for | Continue labeled loop |
continue:label value | Labeled for...yield | Substitute in labeled yield |
The following uses are compile-time errors:
breakorcontinueoutside any loop: errorbreak valueinfor...doorwhile...do: error (E0860) — these forms have typevoidcontinue valueinlooporwhile: error (E0861) — these loops do not accumulate valuescontinue:label valuetargeting afor...do: error (E0873)- Reference to undefined label: error
- Label shadowing: error (E0871)