if / else
if chooses a path based on a boolean condition. As a statement, it runs a
block:
Classic if / else if / else.
01-if-statement.zolo
// Feature: If as a statement
// Syntax: `if cond { ... } else if cond { ... } else { ... }`
// When to use: branching execution with side effects
// (logs, mutation, early-return). When you just want to run code
// and don't need a value back, use the statement form.
let x = 15
// Simple form: just if.
if x > 10 {
print("x is greater than 10")
}
// if / else.
if x % 2 == 0 {
print("even")
} else {
print("odd")
}
// if / else if / else chain — evaluated top-down,
// stops at the first truthy branch.
if x > 100 {
print("huge")
} else if x > 50 {
print("large")
} else if x > 10 {
print("medium")
} else {
print("small")
}
// expected: medium
But if is also an expression: each branch is a value, and the whole if
"becomes" that value — no temporary variable needed.
The chosen branch is the value of the expression.
02-if-expression.zolo
// Feature: If as an expression (returns a value)
// Syntax: `let v = if cond { val_a } else { val_b }`
// When to use: replace the ternary; compute a value
// dependent on a condition in a single expression without
// needing an intermediate mutable variable.
// The last value of each block is the value of the expression.
let x = 10
let label = if x > 5 { "large" } else { "small" }
print(label) // large
// Chain in expression form — works the same as the statement.
fn classify(n: int) -> str {
return if n > 0 {
"positive"
} else if n < 0 {
"negative"
} else {
"zero"
}
}
print(classify(7)) // positive
print(classify(-3)) // negative
print(classify(0)) // zero
// Useful in arithmetic/format expressions.
fn abs(n: int) -> int {
return if n < 0 { -n } else { n }
}
print(abs(-42)) // 42
// Works in interpolation too — `if` is just an expression.
let n = 3
let parity = if n % 2 == 0 { "even" } else { "odd" }
print("number {n} is {parity}")
// expected: number 3 is odd
When the condition is "does this value match this pattern?", use if-let:
Matches a pattern and binds the variables only inside the block.
03-if-let.zolo
// Feature: `if let` — pattern match in a conditional
// Syntax: `if let Pattern = expr { ... } else { ... }`
// When to use: extract a value from Option/enum when you only
// care about one case (shortcut for `match` with 2 arms).
enum Opt {
Some(int),
None,
}
let v1 = Opt.Some(42)
let v2 = Opt.None
// Successful match — `x` is available inside the block.
if let Opt::Some(x) = v1 {
print("Got: {x}") // Got: 42
} else {
print("empty")
}
// Match fails — falls through to else.
if let Opt::Some(x) = v2 {
print("Got: {x}")
} else {
print("empty") // empty
}
// `if let` also works for direct optional `T?`.
let maybe: int? = 7
if let v = maybe {
print("double: {v * 2}") // double: 14
}
// As an expression, returning a value.
let text = if let Opt::Some(n) = v1 { "n={n}" } else { "nothing" }
print(text) // n=42
And when the failure path must exit early (return/break), use let-else:
Destructures on the happy path; diverges in the else.
04-let-else.zolo
// Feature: `let else` — destructuring with early-return
// Syntax: `let Pattern = expr else { return / panic / break ... }`
// When to use: you want to destructure (refutable) and diverge
// on failure. Keeps the "happy path" without the extra indentation
// of `if let`.
// Take first element or return -1.
fn first(arr: [int]) -> int {
let [head, ..rest] = arr else {
return -1
}
return head
}
print(first([10, 20, 30])) // 10
print(first([])) // -1
// Destructuring an enum in let-else.
enum Resp {
Ok(int),
Err(str),
}
fn double_or_zero(r: Resp) -> int {
let Resp::Ok(n) = r else {
return 0
}
return n * 2
}
print(double_or_zero(Resp.Ok(21))) // 42
print(double_or_zero(Resp.Err("x"))) // 0
See also