Saltar al contenido

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.

06-optional-chaining.zolo
Playground
// 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.

07-null-coalesce.zolo
Playground
// 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.

08-try-question.zolo
Playground
// 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".

Consulta también

Buscar en Zolo

9 resultados

enespt-br