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.