Seguridad con Nil
En esta página
Cuando un valor puede ser nil, Zolo ofrece tres operadores complementarios
que eliminan comprobaciones explícitas y cadenas verbosas de if.
El operador ?. navega por campos o llama a métodos de forma segura: si el
receptor es nil, toda la expresión hace cortocircuito a nil en lugar de
lanzar un error.
?. en campos de struct y en el resultado de funciones que pueden devolver nil.
// Feature: Optional chaining `?.`
// Syntax: `obj?.field` — if `obj` is `nil`, propagates `nil`;
// otherwise, accesses the field.
// When to use: navigate a chain of fields where any link could be
// `nil`, without nested if-else. Combined with `??` it gives a
// safe default.
struct Address {
city: str,
zip: str,
}
struct User {
name: str,
address: Address,
}
let alice = User { name: "Alice", address: Address { city: "Portland", zip: "97201" } }
// Plain access — no `?.` when you know it is not nil.
print(alice.name) // Alice
print(alice.address.city) // Portland
// Optional chain — safe even if there is a nil in the middle.
let city = alice?.address?.city ?? "Unknown"
print(city) // Portland
// When user is nil, `?.` short-circuits.
let missing: User? = nil
let city2 = missing?.address?.city ?? "Unknown"
print(city2) // Unknown
// Useful in lookups that may fail.
fn find(id: int) -> User? {
if id == 1 { return alice }
return nil
}
let zip1 = find(1)?.address?.zip ?? "00000"
let zip2 = find(99)?.address?.zip ?? "00000"
print(zip1) // 97201
print(zip2) // 00000
// Chain on a method also works.
let upper = find(1)?.name.to_upper() ?? "?"
print(upper) // ALICE
El operador ?? define un valor por defecto cuando el lado izquierdo es
nil. A diferencia de ||, solo se activa para nil — valores falsy como
0, false o cadena vacía pasan sin disparar el fallback. Es habitual
encadenar ?. y ?? en la misma expresión.
Fallback simple, encadenamiento de ?? y la distinción entre nil y valores falsy.
// Feature: Null-coalescing operator `??`
// Syntax: `value ?? fallback` — if `value` is `nil`, returns
// `fallback`; otherwise, returns `value`.
// When to use: define a default when something can be `nil`.
// Reads as: "or else". Unlike `||`, it does NOT treat `0`, `false`
// or empty string as falsy — only `nil` triggers the fallback.
// Simple default.
let name: str? = nil
print(name ?? "Anonymous") // Anonymous
let real_name: str? = "Alice"
print(real_name ?? "Anonymous") // Alice
// Chained defaults.
let primary: str? = nil
let secondary: str? = nil
let tertiary = "Default"
let chosen = primary ?? secondary ?? tertiary
print(chosen) // Default
// Combined with `?.` — the classic safe-navigation case.
struct Config {
theme: str,
}
let cfg: Config? = nil
let theme = cfg?.theme ?? "dark"
print(theme) // dark
// `??` does NOT treat 0/false as nil — only nil itself.
let zero: int? = 0
print(zero ?? 42) // 0 (not 42!)
let empty: str? = ""
print(empty ?? "fallback") // (empty string)
// Result of a function that may fail.
fn lookup(key: str) -> int? {
if key == "a" { return 1 }
if key == "b" { return 2 }
return nil
}
print(lookup("a") ?? -1) // 1
print(lookup("z") ?? -1) // -1
El operador postfijo ? es la "propagación de nil": dentro de una función cuyo
tipo de retorno es T?, escribir expr? hace que la función retorne nil
inmediatamente si expr es nil. Es el equivalente al ? de Rust aplicado
a opcionales.
? encadena funciones fallidas sin if let en cada paso; cualquier nil aborta
y se propaga al llamador.
// Feature: Try operator `?` (early-return on optional)
// Syntax: `expr?` — if `expr` is `nil`, returns `nil` from the
// current function; otherwise, "unwraps" the value.
// When to use: chain calls that may fail (`-> T?`) without a nested
// if-let on every step. The CALLING function must also return `T?` —
// `?` propagates the nil upward.
// Function that may fail.
fn parse_int(s: str) -> int? {
if s == "x" { return nil }
if s == "" { return nil }
return 42 // mock — pretends it parsed
}
// `?` propagates nil. If `parse_int` returns nil, `process` also
// returns nil without needing `if let`/`match`.
fn process(s: str) -> int? {
let v = parse_int(s)? // if nil, returns nil from here
return v + 1
}
print(process("ok") ?? -1) // 43
print(process("x") ?? -1) // -1 (parse failed, ? propagated nil)
print(process("") ?? -1) // -1
// Chain of stages — any failure aborts.
fn lookup_name(id: int) -> str? {
if id == 1 { return "Alice" }
return nil
}
fn first_letter(s: str) -> str? {
if s.len() == 0 { return nil }
return s.sub(1, 1)
}
fn initial(id: int) -> str? {
let name = lookup_name(id)? // may fail
let letter = first_letter(name)? // may fail
return letter.to_upper()
}
print(initial(1) ?? "?") // A
print(initial(99) ?? "?") // ? (lookup failed)
// `?` also works inside expressions.
fn double_if_found(id: int) -> str? {
return lookup_name(id)?.to_upper()
}
print(double_if_found(1) ?? "?") // ALICE
print(double_if_found(99) ?? "?") // ?
Desafío
Escribe una función greet(id: int) -> str? que use lookup_name(id)? del
ejemplo y devuelva "¡Hola, {name}!" cuando el id exista, o nil cuando no.
Prueba con greet(1) ?? "no encontrado" y greet(99) ?? "no encontrado".