Bindings: let, let mut and var
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.