03 · Variables y Bindings
Shadowing y Alcance
Redeclarar un nombre con let (shadowing) y cómo el alcance léxico define el tiempo de vida de un binding.
En esta página
El shadowing consiste en redeclarar un nombre con un nuevo let. Cada let
crea un binding completamente nuevo — el anterior queda inaccesible, sin
necesidad de mut. Es útil en pipelines de transformación donde el nombre lógico
permanece igual pero el tipo o el valor cambia en cada paso:
let value = "42" → let value = 42 → let value = value * 2; cada let crea un binding nuevo.
// Feature: Shadowing — redeclaring a name with `let`
// Syntax: `let x = ...` followed by another `let x = ...`
// When to use: transform a value in stages while keeping the same
// name (parse → validate → normalize), change a variable's type
// during a pipeline. Unlike `mut`, every `let` creates a NEW
// binding; the previous one becomes inaccessible.
// Shadowing with type change.
let value = "42" // str
let value = 42 // int (new binding)
let value = value * 2 // int = 84
print(value) // 84
// Useful for "cleaning" data in a pipeline.
let raw = " Hello "
let raw = raw.trim() // "Hello"
let raw = raw.to_upper() // "HELLO"
print(raw) // HELLO
// Shadowing inside blocks (scope).
let x = 10
{
let x = 100 // shadows only inside the block
print(x) // 100
}
print(x) // 10 (outer binding intact)
// Difference vs `mut`:
// - `let mut x = 1; x = 2` -> same binding, new value
// - `let x = 1; let x = 2` -> new binding, still immutable
let count = 1
let count = count + 1
let count = count + 1
print(count) // 3 (each `let` creates a fresh binding)
El alcance léxico determina dónde se puede usar un binding. Cada bloque
{ ... } abre un nuevo alcance: los bindings declarados dentro mueren al cerrar
} y no se filtran hacia afuera. Las funciones, if, for y while siguen la
misma regla:
Los bindings internos no existen fuera del bloque; if let restringe el binding a la rama verdadera.
// Feature: Lexical binding scope
// Syntax: bindings live in the `{ ... }` they were declared in
// When to use: to understand when a variable "dies" and where it
// can be used. Zolo uses lexical scoping, like Rust and JS `let`.
// A nested block has its own scope.
let outer = "outside"
{
let inner = "inside"
print(outer) // outside — outer visible
print(inner) // inside
}
// inner does not exist anymore here (uncomment to test):
// print(inner) // error: undefined variable
print(outer) // outside
// Function scope — params and local bindings disappear on return.
fn compute(n: int) -> int {
let doubled = n * 2
let result = doubled + 10
return result
}
print(compute(5)) // 20
// `doubled` and `result` did not leak outside.
// `if`/`for`/`while` introduce a new scope.
var acc = 0
for i in 1..=3 {
let squared = i * i // lives only inside the loop
acc = acc + squared
}
print(acc) // 1 + 4 + 9 = 14
// `if let` bindings only apply inside the true branch.
let value: int? = 42
if let v = value {
print("inside: {v}") // v exists here
}
// v does not exist outside.
Desafío
En el ejemplo de shadowing, reemplaza los let sucesivos por un único let mut
con reasignaciones. La salida final debe ser la misma — compara ambos enfoques y
decide cuál expresa mejor la intención.
Consulta también