Evaluation Order

Source-order evaluation, short-circuiting, assignment order, defers, cleanup, and operator precedence.

Evaluation order is part of the current language semantics. Except for constructs that short-circuit or select one branch, subexpressions are evaluated left-to-right in source order, and each subexpression's side effects complete before the next subexpression begins.

Expression Order

  • Unary operators evaluate their operand before applying the operator.
  • Casts evaluate the value before performing the checked conversion.
  • Binary operators evaluate the left operand before the right operand.
  • && evaluates the right operand only when the left operand is truthy.
  • || evaluates the right operand only when the left operand is falsy.
  • condition ? then_expr : else_expr evaluates the condition first, then only the selected branch.
  • A call evaluates the callee expression first.
  • A method call evaluates the receiver before all explicit arguments.
  • Call arguments are evaluated in parameter order after named and default arguments are normalized.
  • Struct literal fields are evaluated in struct declaration order, including default field values.
  • Array, vector, and tuple literal elements are evaluated left-to-right.
  • Member access, optional member access, and subscript expressions evaluate the receiver or base expression first.
  • Subscript index expressions are evaluated left-to-right after the base expression.

Example:

import std.io;

fn log(label: str, value: i32) -> i32 {
    io.println("{label}");
    return value;
}

fn main() {
    let result = log("left", 1) + log("right", 2);
    io.println("result={result}");
}

Output:

left
right
result=3

Short-circuiting evaluates only the branch it needs:

fn mark(flag: &bool) -> bool {
    flag = true;
    return true;
}

fn main() {
    let right = false;
    let ok = false && mark(&right);

    assert !ok;
    assert !right;
}

Assignment Order

Assignment evaluates the target first, including the receiver, base, member path, and index expressions needed to identify the destination. It then evaluates the right-hand side and stores the result.

Compound assignment evaluates the target, loads the old value, evaluates the right operand, computes the new value, and stores it.

fn main() {
    let values = [1, 2, 3];
    let i = 0;

    values[i] += 10;

    assert values[0] == 11;
}

A declaration initializer evaluates its right-hand expression before the binding is initialized. Tuple destructuring evaluates the tuple expression once, then initializes bindings left-to-right from tuple elements.

Defer and Cleanup

defer registers its body when control reaches the defer statement. When a block exits normally, defers registered in that block run in last-in, first-out order before automatic cleanups for locals whose lifetime ends at that block.

return, break, continue, throw, and error propagation unwind exited scopes with the same defer-before-cleanup ordering before transferring control.

try expr evaluates expr, unwraps success, and returns the error from the current function on failure.

expr catch { ... } evaluates expr. On success it yields the unwrapped value; on error it evaluates only the matching catch arm.

await expr evaluates expr, consumes the resulting future or scoped task, waits for completion, and yields the result.

Statement Semicolons

These statement forms require a trailing semicolon:

  • let and const
  • return
  • throw
  • assert
  • break
  • continue
  • asm
  • ordinary expression statements

These statement forms do not take a trailing semicolon:

  • defer
  • if
  • while
  • loop
  • for
  • with
  • select
  • nested block statements

Statement-form match and statement-form block catch may be written with or without a trailing semicolon. When either form appears inside a larger statement, the outer statement follows its own semicolon rule.

Precedence Table

Higher precedence binds more tightly.

PrecedenceOperators and formsGrouping
14(), [], ., ?.postfix/member, left-to-right
13prefix !, ~, +, -, *, &, try, awaitprefix
12asleft-to-right; takes a type on the right
11*, /, %left
10..., ..<left
9+, -left
8<<, >>left
7<, >, <=, >=left
6==, !=left
5&left
4^left
3|left
2&&left, short-circuit
1? :, ||, catchternary right; || and catch left
0assignment formsright

Because ? :, ||, and catch share the lowest non-assignment precedence, parenthesize expressions that mix them unless that grouping is intended.