Conditional

The conditional operator chooses one of two values by a condition. It is the only operator with three operands, and is often called the ternary operator.

conditional = condition "?" then-value ":" else-value
then-value  = an expression above level 3, or a parenthesised conditional
else-value  = an expression above level 3, or another conditional
let label = n % 2 == 0 ? "even" : "odd";

When the condition is true the result is the first value, otherwise the second. Only the chosen value is evaluated; the other is skipped, just as an untaken if block is.

The space before ?

A ? written directly after an expression is postfix propagation: Read()? passes a failure on. The conditional's ? is told apart by the whitespace in front of it, so it must not touch the condition:

let a = n > 0 ? 1 : 2;
let b = n > 0? 1 : 2;

The first line is a conditional. In the second, 0? is a propagation of 0, and the line fails to parse with error: expected ';' after the binding declaration before '1'. The space after the ? does not matter, but write one for symmetry.

The condition

The condition must be a bool. A number is not true or false by itself:

error: condition for '?:' must have type 'bool', but found 'int32'

Write the comparison: n % 2 != 0 ? "odd" : "even".

Branch types

The conditional produces one value, so its two branches must have one type. The second converts to the first or the first to the second, as two match arms do; two unrelated types are an error:

let answer = ready ? "yes" : 7;
error: conditional branch type mismatch: expected 'char8[..]', found 'int'
  help: make both branches produce the same type

Where the context fixes the type — an annotated binding, an assignment, a return, a call argument — both branches take it, and an unsuffixed literal in either branch must fit it:

let wide: uint128 = n > 2 ? 18446744073709551616 : 0;

A branch that never produces a value, such as a call to Panic, does not take part:

func Checked(n: int32) -> int32 {
    return n >= 0 ? n : Panic("negative");
}

return, fail, break and continue cannot be a branch — error: 'return' cannot be used as a value here. Use an if, or the fallback of ??, which accepts them.

Nesting

The conditional groups from the right, so a chain in the else branch reads as a list of cases tested in order:

let sign = n < 0 ? "negative" : n == 0 ? "zero" : "positive";

A conditional in the then branch must be in parentheses. The then branch ends at the first :, and an unparenthesised ? inside it stops the parse with error: expected ':' between the conditional expression branches before '?':

let size = n > 0 ? (n > 5 ? "big" : "small") : "none";

Past two or three cases, an if/else if chain or a match expression reads better.

Precedence

The conditional is on level 3, below every operator but ranges and assignment. Its condition takes in everything before the ?, including || and ??:

let total = price - (member ? 5 : 0);

Without the parentheses, price - member ? 5 : 0 would subtract a bool from price and use the result as the condition. Wrap a conditional whenever it sits inside a larger expression.

See also

  • if — choosing between statements rather than values
  • match — choosing among many values
  • Propagation — the postfix ?
  • Learn: Ternary