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:

  • let bindings
  • use imports
  • Assignments (x = v)
  • Compound assignments (x += v)

16.0.4 Value-producing expressions

All other constructs are expressions that produce values:

ExpressionType
if c then a else bUnified type of a and b
if c then avoid (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 evoid
while c do evoid
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:

  1. panic(msg:), todo(), unreachable() — always terminate the program
  2. break and break value — exit the enclosing loop
  3. continue and continue value — skip to the next iteration
  4. expr? when the Err/None branch is taken — returns from the enclosing function
  5. A block { ... e } where the last expression e is terminating
  6. if c then t else e where both t and e are terminating
  7. match expr { arms } where every arm body is terminating
  8. loop { body } with no break — an infinite loop with type Never
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:

OperatorSkips 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:

  1. Call source.iter() to obtain an iterator (via Iterable trait)
  2. Call iterator.next() to obtain (Option<Item>, Iterator)
  3. If Some(value): bind x = value, evaluate body, go to step 2
  4. 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 expr to an accumulating list
  • The result type is [T] where T is the type of expr
  • An empty source produces an empty list []
  • break stops iteration and returns the accumulated values so far
  • break value appends value and returns
  • continue skips this element (nothing appended)
  • continue value appends value instead 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

FormValid inEffect
breakloop, while...do, for...do, for...yieldExit loop
break valueloop, for...yieldExit with value
break:labelLabeled loop, while, forExit labeled loop
break:label valueLabeled loop, for...yieldExit labeled with value
continueloop, while...do, for...do, for...yieldNext iteration
continue valuefor...yieldSubstitute value
continue:labelLabeled loop, while, forContinue labeled loop
continue:label valueLabeled for...yieldSubstitute in labeled yield

The following uses are compile-time errors:

  • break or continue outside any loop: error
  • break value in for...do or while...do: error (E0860) — these forms have type void
  • continue value in loop or while: error (E0861) — these loops do not accumulate values
  • continue:label value targeting a for...do: error (E0873)
  • Reference to undefined label: error
  • Label shadowing: error (E0871)