Pular para o conteúdo

Segurança com Nil

Nesta página

Quando um valor pode ser nil, o Zolo oferece três operadores complementares que eliminam verificações explícitas e encadeamentos verbosos de if.

O operador ?. navega por campos ou chama métodos de forma segura: se o receptor for nil, toda a expressão curto-circuita para nil em vez de lançar um erro.

?. em campos de struct e no resultado de funções que podem retornar 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

O operador ?? define um valor padrão quando o lado esquerdo é nil. Ao contrário de ||, ele só dispara para nil — valores falsy como 0, false ou string vazia passam sem acionar o fallback. É comum encadear ?. e ?? na mesma expressão.

Fallback simples, encadeamento de ?? e a distinção entre nil e 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

O operador postfix ? é a "propagação de nil": dentro de uma função cujo tipo de retorno é T?, escrever expr? faz com que a função retorne nil imediatamente se expr for nil. Isso é o equivalente ao ? do Rust aplicado a opcionais.

? encadeia funções falíveis sem if let a cada passo; qualquer nil aborta e propaga para quem chamou.

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) ?? "?")  // ?

Desafio

Escreva uma função greet(id: int) -> str? que use lookup_name(id)? do exemplo e retorne "Olá, {name}!" quando o id existir, ou nil quando não existir. Teste com greet(1) ?? "não encontrado" e greet(99) ?? "não encontrado".

Veja também

Buscar no Zolo

9 resultados

enespt-br