Shadowing y Alcance
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