Pular para o conteúdo

O livro · 04

Erros

book.md · 94 linhas · 2 min de leitura

Objetivo: tratar falha como valor (Result e error-sets), propagar com catch (sem operador ?), e conhecer noreturn.

Falha tem duas perguntas, e Makoto dá um construto pra cada:

  • Result[T, E]: a resolução, ou seja, deu certo (Ok(T)) ou não (Err(E)).
  • error{...}: a união de erros, ou seja, quais falhas existem (um error-set fechado e estrutural).

Os dois compõem: o E de um Result é um error{...}.

fn open(path: string) -> Result[File, error{NotFound, PermissionDenied}] { ... }

error pelado é aberto (FFI/terceiros, exige _ no match); error{A, B} é fechado (exaustivo, proíbe _); error{} é o conjunto vazio, ou seja, “não erra” (um Result[T, error{}] cujo Err é inconstruível, útil quando um concreto satisfaz uma interface falível mas nunca falha).

Casar o Result inteiro cobre o Ok e cada variante de erro, de forma exaustiva:

match open(path) {
Ok(f) => use(f)
Err(error.NotFound) => criar_default()
Err(error.PermissionDenied) => abortar()
}

No caso sequencial, quando o sucesso continua e só o erro precisa de atenção, use os trailers. O Ok é resolvido pela atribuição; o trailer lida só com o erro. Não existe operador ?: a propagação é explícita.

v := do_it(x) catch |e| { return Err(e) } // propaga (explícito)
v := do_it(x) catch |e| { return e } // açúcar: 'return e' → Err(e)
v := do_it(x) catch |e| { runtime.panic(e) } // aborta

catch |e| trata o erro como unidade; match |e| ramifica por variante (só faz sentido com erro união):

cfg := load() match |e| { // Ok → cfg; cada braço de erro ESCAPA
error.NotFound => return new_config() // early-return por variante
error.Corrupted => runtime.panic("config ilegível")
}

Em value-binding, cada braço do trailer escapa (return/break/panic): o trailer não dá valor ao v. Quando você quer que o erro produza um valor (um default que vira o v), o construto certo é o match sobre o Result inteiro (o fork acima), não o trailer.

Compor error-sets é unir, com o spread nome...:

fn load(p: string) -> Result[Data, error{open..., parse...}] { // une as variantes de open e parse
f := open(p) catch |e| { return e }
d := parse(f) catch |e| { return e }
return Ok(d)
}

Como não há unit, uma operação que dá certo sem produzir valor devolve só o error-set:

fn send(msg: Msg) -> error{ProcessDown} // sucesso = ausência de erro
chan <- data catch |e| { return e } // erro → propaga; sucesso → cai pra próxima linha

Uma função que nunca devolve o controle tem o tipo bottom noreturn. É o que deixa panic/exit servirem de “escape” num catch em value-binding (o caminho de erro diverge, então o v ainda é o tipo do Ok):

fn panic(msg: string) -> noreturn // runtime.panic; idem os.exit, event-loops infinitos
x := parse(s) catch |e| { runtime.panic(e) } // x tipa como int: o braço de erro diverge

Próximo: 05 · Coleções e strings