Skip to content

Conditionals

Boolean branches, branch values, pattern conditions, and early exits.

Updated View as Markdown

if selects a branch from a boolean condition.

if ready {
    start()
} else {
    wait()
}

The condition must have type bool. Atoll does not convert numbers, strings, collections, or optional values to booleans.

Common condition forms are:

Form Requirement
if condition condition has type bool
if Pattern := value Pattern is compatible with value
Pattern := value else { ... } the pattern may fail and the else block diverges

Use an explicit comparison such as items.is_empty(), count != 0, or maybe_value.is_some(); there is no general truthiness conversion.

Branch values

An if is an expression. Its selected branch can produce a value.

label := if score >= 90 {
    "excellent"
} else if score >= 60 {
    "passing"
} else {
    "retry"
}

When a value is required, every normally completing path must produce a compatible type. Branches can contribute the same type directly, coerce to an expected type, or form an inferred anonymous union where the type system permits one.

value: float = if exact {
    1.0
} else {
    estimate()
}

An expected type is checked branch by branch. This lets a scalar widen, a bare value lift into an optional, or a member inject into an anonymous union without first constructing an intermediate variable.

Without an expected type, the checker joins the branch results:

value := if use_number {
    42
} else {
    "forty-two"
}
// `value` is `int | string`.

Tuple branches join element by element when their arity agrees. A branch that cannot be joined or injected produces a type diagnostic at the branch, not a runtime tag failure.

An if without else is used for effects and has no useful branch value.

if debug_enabled {
    println(state)
}

Chains

else if expresses an ordered sequence. Conditions are evaluated only until one branch is selected.

category := if value < 0 {
    "negative"
} else if value == 0 {
    "zero"
} else {
    "positive"
}

Use match when the decision is fundamentally about variants, destructuring, or exhaustively covering a closed type.

Pattern conditions

if pattern := expression runs the first branch only when the expression matches the pattern.

if Some(user) := cache.get(user_id) {
    greet(user)
} else {
    load_user(user_id)
}

Bindings introduced by the pattern exist only in the successful branch.

if Ok(value) := parse(input) {
    consume(value)
} else if input.is_empty() {
    use_default()
} else {
    report_invalid(input)
}

The expression is evaluated once. The pattern may be a variant, tuple, struct, slice, literal, range, or another supported pattern form.

The failed branch cannot read bindings that the pattern did not initialize:

if Some(value) := maybe_value {
    consume(value)
} else {
    // `value` is not in scope here.
    report_missing()
}

Refutable bindings

A refutable local binding can put its failure branch after else.

Some(user) := find_user(id) else {
    return None
}

return Some(user.name)

Unlike if pattern bindings, the successful names remain visible after the statement. The else block must diverge so execution cannot continue without those names initialized.

Valid divergence includes return or error, and break or continue when the binding is inside a loop. Merely calling a void function or ending the block is not enough.

Some(port) := configured_port else {
    error MissingPort
}

connect(port)?

Early exits

A branch that always returns, propagates an error, breaks, or continues does not constrain the value type of a normally completing branch.

result := if input.is_empty() {
    return default_value()
} else {
    parse(input)?
}

This follows the never/diverging type rule; it is not a special-case conversion for if.

Evaluation

The condition is evaluated first. Exactly one selected branch runs. Branches that are not selected have no runtime effects.

value := if cached {
    read_cache()
} else {
    fetch_remote()
}

All branches are still parsed and type-checked. “Not selected” is a runtime statement, not conditional compilation; use a data-layer when query only for the specific compile-time query feature.

There is no separate ternary operator. Use a compact if expression when both branches are short.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close