Erros
A redefinição: erro é valor, não evento de controle.
Result[T,E]+ error-sets, propagação explícita (sem?), e nada deunit.
O conceito
Seção intitulada “O conceito”Há duas grandes escolas para falha. Exceções (Java/Python/C++): o erro é um evento que “explode” e sobe a stack até alguém pegar, invisível no tipo da função. Erro-como-valor (Go/Rust): a falha é um valor de retorno que você inspeciona. Makoto fica firmemente na segunda escola, e radicaliza a explicitude.
Como Makoto redefine
Seção intitulada “Como Makoto redefine”Falha tem duas perguntas, e há um construto para cada:
Result[T, E]é a resolução:Ok(T)ouErr(E). É uma família de aridade 2, igual aMap[K,V], então compõe com generics (mapsobre oOk,collectdeList[Result[T]]paraResult[List[T]]). Quem recebe umResultnão pode ignorar a falha: ela está no tipo.error{...}é a união: quais falhas existem. Fechado (error{A, B}, exaustivo, proíbe_), aberto (errorpelado, exige_), ou vazio (error{}= “não erra”,Errinconstruível).
Propagação é explícita, por trailer, nunca por operador: v := do() catch |e| { return e } propaga;
catch |e| trata o erro como unidade; match |e| ramifica por variante numa tacada (vincula e
discrimina, sem o duplo-binding “primeiro catch, depois match”). E sucesso-sem-valor não usa
unit: é o error-set sozinho (fn send(m) -> error{...}), onde sucesso = ausência de erro.
noreturn é o tipo bottom da divergência (panic/exit), que faz um braço de erro “escapar” sem dar
valor.
O raciocínio
Seção intitulada “O raciocínio”A inspiração declarada é Go (forçar a árvore de recuperação a ser desenhada linha a linha), mas com
uma torção: em vez de repetir if err != nil, você escreve catch/match |e| uma vez, e o
compilador exige que você trate ou propague de propósito. O ganho central é matar o hidden flow:
você nunca propaga um erro por acidente; cada passo é uma decisão visível (“trato aqui ou repasso?”).
A escolha contra unit é economia conceitual pura: não multiplicar tipos vazios. Se o sucesso não
carrega valor, o error-set sozinho já diz tudo; se você precisa de sucesso como valor de composição,
Result[(...), E] resolve, mas o caso comum (procedimento que pode falhar) não paga por um tipo
fantasma.
Por que não as alternativas
Seção intitulada “Por que não as alternativas”- Exceções. Fluxo de controle escondido: você chama
fetch_user(id)e ela pode ejetarNetworkError/ParseError/Timeoutpara qualquer nível da stack, sem nada no tipo. O contrato de falha fica invisível. Erro-como-valor o torna parte da assinatura. - O operador
?(Rust/Zig). É o mesmo perigo do coloring incontível, aplicado a erro: vira a forma de fugir de tratar a falha, um glifo que propaga sozinho. A linguagem deve fazer o custo da propagação visível;catch |e| { return e }é mais longo de propósito, e é essa fricção que mata a negligência. (Note o paralelo comunsafe: a diferença é queunsafeé contível e?só esconde.) unit/voidcomo tipo. Tipo vazio que não carrega informação nenhuma; o error-set pelado ocupa o lugar sem o fantasma.-> (T, error)ao estilo Go cru. Permite onil, nilambíguo e não compõe com generics. Makoto empurra para a forma canônica:-> (u8, error)gera um aviso “useResult[u8, error]”.
A dor concreta
Seção intitulada “A dor concreta”A função em Java cujo throws mente (ou não existe), e o NullPointerException que sobe de seis
camadas abaixo. O unwrap()/? em Rust que vira hábito e engole falhas em código que deveria ser
robusto. O if err != nil repetido de Go que é chato mas (reconhece-se) funciona, porque força
você a olhar cada falha. Makoto quer o efeito de Go (atenção obrigatória a cada falha) sem a repetição
e sem a porta de fuga do ?.
O modelo mental
Seção intitulada “O modelo mental”Erro é um valor que você trata ou propaga, explicitamente. Falha é uma saída do programa, não um evento que o runtime resolve por você.
A fricção aceita
Seção intitulada “A fricção aceita”Verbosidade é o preço, e é deliberado: você não encadeia do_a()?.do_b()?.do_c()?; monta a árvore
de recuperação passo a passo. O spread de error-sets (error{open..., parse...}) adiciona um pouco de
maquinaria, mas é o que evita reenumerar a lista inteira de variantes de cada subsistema. A aposta:
cada linha que propaga custar a ser escrita é o que garante que nenhuma falha é esquecida.
Próximo: 06 · Ausência