A premissa
Falha em Zero não é um fluxo de controle paralelo separado. É parte do fluxo de controle normal, declarado na assinatura da função e reconhecido em cada ponto de chamada. Duas peças fazem isso funcionar:
raisesna assinatura da função — "esta função pode falhar".checkno ponto de chamada — "se isso falhar, faça a minha função falhar com o mesmo erro".
A combinação é suficiente para expressar o que a maioria das linguagens recorre a try/catch ou a tipos Result para resolver.
Declarando uma função falível
Adicione raises depois do tipo de retorno:
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
Duas formas de ler a assinatura:
- "
validateretorna umi32ou lançaInvalidInput." - "O conjunto de resultados possíveis é {
i32,InvalidInput}."
Ambas estão corretas. O compilador rastreia as duas possibilidades e exige que quem chama faça algo explícito sobre cada uma.
Você também pode escrever uma cláusula raises simples:
pub fun main(world: World) -> Void raises {
check world.out.write("hello\n")
}
raises (sem lista de erros) significa "esta função pode falhar com qualquer erro". Em main, essa é a forma convencional — o programa pode sair com status diferente de zero se algo der errado, e o runtime cuida de expor o erro.
Para funções mais fundas na pilha de chamadas, prefira a forma explícita raises { ErroA, ErroB } para que os modos de falha fiquem documentados em cada nível.
Lançando um erro
Dentro de uma função falível, raise sai da função com o erro indicado:
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
raise InvalidInput produz um erro InvalidInput naquela linha. A função não continua além desse ponto — o controle volta para quem chamou, e o chamador vê o erro em vez de um i32. A cláusula raises lista os únicos tipos de erro que essa função pode lançar; lançar algo fora da lista é erro de compilação.
Propagando um erro com check
Quem chama uma função falível precisa reconhecer a possível falha. O reconhecimento mais comum é check:
fun run() -> Void raises { InvalidInput } {
check validate(true)
}
check validate(true) faz duas coisas:
- Chama
validate(true). - Se
validatelançou um erro, propaga para cima —runlança o mesmo erro para quem chamou ela.
Para a propagação ser permitida, run precisa declarar que pode lançar InvalidInput (ou algo compatível) na sua própria cláusula raises. O compilador verifica. Se a assinatura de run dissesse raises { OtherError }, a propagação não compilaria porque InvalidInput não está no conjunto.
Um exemplo completo, das amostras oficiais da linguagem — clique em Run para ver a propagação dar certo:
O tipo do erro viaja pela assinatura da função até o topo da pilha de chamadas. O raises simples de main aceita qualquer coisa que run possa lançar, então a propagação aterriza bem.
Por que não try/catch?
A disciplina de design por trás de raises/check é que a falha nunca é invisível num ponto de chamada. Numa linguagem com try/catch, uma exceção pode passar silenciosamente por uma função que nem sabe que está envolvida — a função parece pura, mas alguma chamada lá no fundo do corpo lança e a exceção sobe por ela.
Isso é conveniente para o autor do código que lança. Custa caro para todos os outros:
- Leitores (e agentes) não conseguem dizer pela assinatura se uma função participa de caminhos de falha.
- Refatorar fica tenso — mover uma chamada entre funções pode mudar quais exceções são alcançáveis.
- O código de recuperação fica longe do lugar que sabe o que fazer.
Zero paga o custo na entrada — anotações em toda função falível e check em toda chamada falível — para conseguir a propriedade "os modos de falha em que uma função participa estão visíveis na assinatura". É uma propriedade em que tanto humanos quanto agentes podem confiar.
Por que não só Result<T, E>?
Você poderia expressar a mesma coisa com um choice — um tipo Result<T, E> com variantes ok e err. Zero te dá esse padrão também; é uma ferramenta útil quando a falha é dado que você quer inspecionar, guardar ou passar adiante.
O que raises/check acrescenta é uma convenção de nível sintático para o caso comum: "se isso falhar, faça a minha função falhar do mesmo jeito". Sem isso, toda chamada seria envolvida num match que quase sempre re-empacotaria o erro no próprio Result do chamador. check é o atalho para isso, com o compilador garantindo que a propagação seja bem-tipada.
Então:
Result<T, E>(um choice) — quando você quer inspecionar ou carregar a falha como um valor.raises+check— quando você só quer propagar a falha pela pilha de chamadas.
Os dois estão disponíveis; cobrem necessidades ergonômicas diferentes.
Múltiplos tipos de erro
Uma função pode lançar mais de um tipo de erro:
fun parse(input: String) -> i32 raises { Empty, Malformed } {
if std.mem.len(input) == 0 {
raise Empty
}
// ... lógica de parsing ...
raise Malformed
}
Quem chama pode:
- Propagar com
check parse(input)se a assinatura listar tantoEmptyquantoMalformed(ou um superconjunto). - Tratar um ou ambos os erros explicitamente com
matchou com construções no estilotryque a linguagem expuser para esse fim.
A sintaxe exata para tratamento granular (fazer match em tipos de erro específicos vs. propagar os outros) é uma das superfícies que pode mudar no Zero pré-1.0. O contrato — todo tipo de erro que uma função pode lançar está na assinatura — é a parte estável.
Um modelo mental
O sistema de falha do Zero é uma versão estritamente honesta do que toda linguagem imperativa tem:
| Conceito | Try/Catch | Zero |
|---|---|---|
| Marcar uma função como falível | nada (implícito) | raises { ... } |
| Lançar um erro | throw e | raise E |
| Propagar para o chamador | sobe invisivelmente | check call(...) |
| Tratar localmente | try { ... } catch(e) { ... } | match no Result retornado, ou uma forma tipada de tratamento |
A diferença de comportamento é pequena. A diferença em anotações é grande — e proposital. Efeitos são explícitos. Falhas são efeitos.
Notas de estilo
- Use
checklivremente. É o padrão certo quando você não tem nada específico para fazer nessa camada. - Evite
raisessimples em funções auxiliares internas. Quanto mais estreito o conjunto de erros, mais útil a assinatura. - Combine operações falíveis com as capacidades de que precisam. Uma função que usa
Worldpara escrever no stdout quase sempre querraisesporque a escrita pode falhar.
A seguir: Diagnósticos em JSON
raises/check trabalham lado a lado com os diagnósticos do compilador Zero — quando você escreve check contra uma função que não declara os erros certos, o compilador te diz exatamente o que está errado em forma estruturada. A próxima documentação cobre os diagnósticos em JSON — o feed legível por máquina que um agente lê para reparar código.
Perguntas frequentes
O que raises significa em Zero?
raises significa em Zero?raises na assinatura de uma função declara que a função pode falhar. Um raises simples permite qualquer tipo de erro. Uma forma específica como raises { InvalidInput } restringe a função a falhar apenas com os tipos de erro listados. Quem chama precisa reconhecer a possibilidade de falha — seja com check, seja com outra forma explícita de tratamento.
O que o operador check faz?
check faz?check expr avalia expr e, se produzir um erro, propaga esse erro para quem chamou a função atual. Pense como 'execute isso e, se falhar, faça quem me chamou falhar com o mesmo erro'. Para a cláusula raises do chamador permitir a propagação, o chamador também precisa declarar que pode lançar um erro compatível.
Como você lança um erro em Zero?
Use raise NomeDoErro dentro de uma função cuja cláusula raises inclua esse erro. Exemplo: if ok == false { raise InvalidInput }. A função sai naquele ponto; o erro vira o resultado da função, que o chamador pode checar com check.
Por que Zero não usa try/catch?
Try/catch deixa exceções passarem em silêncio por funções que não fazem ideia do que está acontecendo. O design do Zero é que toda assinatura de função precise reconhecer os modos de falha em que participa. Não há fluxo de controle escondido — se uma função pode falhar, a assinatura diz, e todo chamador precisa reconhecer explicitamente com check.
Uma função Zero pode lançar múltiplos tipos de erro?
Sim — liste-os na cláusula raises { ... }, separados por vírgula (ou seguindo a sintaxe atual do Zero para conjuntos de erros). O conjunto de erros possíveis é parte do contrato da função, igual aos parâmetros e ao tipo de retorno. Quem chama pode fazer pattern matching para saber qual erro foi lançado ou simplesmente propagar com check.