Variables & Scope
TLang uses let bindings. Every binding is immutable — once declared, a name holds its value until the scope ends. To update a value, rebind the name with a new let.
let Bindings
Declare a variable with let. The type annotation is optional; the compiler infers it from the value.
let name = "Alice"
let count = 0
let pi = 3.14159
let items = List.of("a", "b", "c")
With explicit type annotation.
let name: String = "Alice"
let count: Int = 0
Rebinding
let does not mutate — it shadows the previous binding with a new one. The old binding still exists in the outer scope but is hidden.
let count = 0
let count = count + 1 // shadows previous — count is now 1
let count = count + 1 // count is now 2
This is the standard pattern for accumulating a value in a loop.
let result = ""
for (item in items) {
let result = result + item + "\n"
}
return result
Scope
Bindings are lexically scoped to the block they are declared in. An inner binding shadows an outer one with the same name; the outer binding is restored when the block exits.
let x = 10
if (condition) {
let x = 20 // shadows outer x inside this block
Terminal.println(x) // prints 20
}
Terminal.println(x) // prints 10 — outer x restored
Function bodies are their own scope.
func compute(n: Int): Int {
let x = n * 2 // local to this function
let x = x + 1
return x
}
// x is not accessible here
Rebinding in for Loops
Inside a for loop body, rebinding a variable declared before the loop updates the outer binding. This is the correct accumulator pattern.
let total = 0
for (n in List.of(1, 2, 3)) {
let total = total + n // updates the outer total each iteration
}
// total is now 6
Contrast with while loops, where rebinding behaves the same (shadows within body, restores on exit).
Expression if
if can be used as an expression, assigning its result to a let. The else branch is mandatory.
let label = if (n < 0) "negative" else if (n == 0) "zero" else "positive"
let status = if (ok) "success" else "failure"
return if (debug) "verbose: " + result else result
match Expression
match can also be used as an expression. A default case is required.
let mime = match (ext) {
case "html" => "text/html"
case "json" => "application/json"
case "css" => "text/css"
default => "text/plain"
}