03 · Variables and Bindings
Bindings: let, let mut and var
Creating immutable bindings with let, mutable ones with let mut, and the var shorthand.
On this page
In Zolo, every binding starts with let and is immutable by default — the
compiler prevents reassignment, eliminating a whole class of aliasing bugs at
zero runtime cost:
let creates an immutable binding; the type is inferred from the initial expression.
// Feature: Immutable binding with `let`
// Syntax: `let name = expression`
// When to use: the default case — every value that does NOT need to
// be reassigned. Immutability by default avoids aliasing bugs.
// `let` creates an immutable binding; the type is inferred.
let name = "Alice"
let age = 30
let pi = 3.14159
let active = true
print(name) // Alice
print(age) // 30
print(pi) // 3.14159
print(active) // true
// Reassigning a `let` is a compile error (uncomment to test):
// name = "Bob" // error: cannot assign to immutable binding
// Immutable bindings accept any expression as their initial value.
let total = 10 + 20 * 2
let greeting = "Hello, " + name + "!"
print(total) // 50
print(greeting) // Hello, Alice!
When a value needs to change over time — a counter, an accumulator — add mut to
the declaration:
let mut allows reassignment and compound-assignment operators (+=, *=, -=).
// Feature: Mutable binding with `let mut`
// Syntax: `let mut name = value` — then `name = new_value`
// When to use: counters, accumulators, any state that changes
// inside loops or branches. The `mut` keyword is required — Zolo
// treats mutability as opt-in to keep intent visible.
// Mutable: needs `mut` in the declaration.
let mut counter = 0
counter = counter + 1
counter = counter + 1
counter = counter + 1
print(counter) // 3
// Classic accumulator in a loop.
let mut sum = 0
for i in 1..=10 {
sum = sum + i
}
print(sum) // 55
// Reassignment respects the type inferred at declaration.
let mut score: int = 100
score = 90
score = 80
print(score) // 80
// Compound assignment — equivalent to `x = x + 5`.
let mut x = 10
x += 5
x *= 2
x -= 1
print(x) // 29
var is an exact alias for let mut. Use var when you want to save
keystrokes or when you plan to use storage classes (var<lazy>,
var<persistent>, etc.) — the <...> syntax only exists on var:
var and let mut are interchangeable; only var accepts storage modifiers.
// Feature: `var` — alias for `let mut`
// Syntax: `var name = value` — then `name = new_value`
// When to use: anywhere `let mut` is valid. Same semantics, fewer
// keystrokes, and the gateway to storage classes (`var<...>`).
// `let mut` continues to work; both are accepted indefinitely.
// Mutable binding via the new shorter form.
var counter = 0
counter = counter + 1
counter = counter + 1
counter = counter + 1
print(counter) // 3
// Equivalent in every way to `let mut counter = 0`.
let mut also_counter = 0
also_counter = also_counter + 1
print(also_counter) // 1
// Compound assignment works the same.
var score = 100
score += 10
score -= 5
print(score) // 105
// Type annotations also work.
var ratio: float = 0.5
ratio = ratio * 2
print(ratio) // 1.0
// Why prefer `var`? Storage classes use the `var<...>` syntax —
// see 12-var-lazy.zolo, 13-var-persistent.zolo, etc. `let mut` does
// not accept storage classes (would be ambiguous to parse).
Challenge
Declare an accumulator var total = 0 and add the numbers from 1 to 100 with a
for loop. Confirm that the result is 5050.