Erros
Erros são valores no Talor: não existe exceção, não existe try/catch, não existe null. A ausência é uma variant, a falha é outra, e as duas passam pelo sistema de tipos.
Option<T>: ausência
let a: Option<i64> = Some(3);
let b: i64? = None; // T? is sugar for Option<T>Result<T, E>: falha
fn parse(s: string): Result<i64, string> {
if s == "" { return Err("empty"); }
Ok(42)
}Option<T> é Some(T) | None; Result<T, E> é Ok(T) | Err(E). Os dois são lidos por match como qualquer variant.
?: propagar
let v = parse(text)?; // returns Err(e) from the enclosing function on Err
let w = a?; // returns None from an Option-returning function? funciona em Option<T> dentro de uma função que devolve Option<_>, e em Result<T, E1> dentro de uma função que devolve Result<_, E2>. Um Err sai pela primeira destas regras que se aplica:
E1éE2: o erro sai como está.- O programa declara
impl Convert<E2> for E1(Convertestá emstd.error): o erro sai comoe.convert(). E2é uma variant com exatamente um caso que guarda umE1e nada mais: o erro sai nesse caso.
Qualquer outra coisa é recusada no ?. Uma função cujo corpo usa ? escreve o seu tipo de retorno.
use std.error.{Convert};
struct ParseError { text: string }
variant AppError { Parse(ParseError), Limit(i64) }
struct Code { n: i64 }
impl Convert<AppError> for Code {
fn convert(self): AppError { AppError.Limit(self.n) }
}
fn parse(s: string): Result<i64, ParseError> {
if s == "" { return Err(ParseError { text: "empty input" }); }
Ok(s.len())
}
fn check(n: i64): Result<i64, Code> {
if n > 3 { Err(Code { n: n }) } else { Ok(n) }
}
fn load(s: string): Result<i64, AppError> {
let n = parse(s)?; // ParseError leaves as AppError.Parse
let m = check(n)?; // Code leaves through its Convert
Ok(m)
}
fn main(): i32 {
for input in ["ab", "", "abcdef"] {
match load(input) {
Ok(n) => println(`'${input}': ${n}`),
Err(AppError.Parse(p)) => println(`'${input}': ${p.text}`),
Err(AppError.Limit(k)) => println(`'${input}': ${k} is over the limit`),
}
}
0
}Error
As falhas da biblioteca padrão respondem ao Error de std.error: e.code() é o número de erro do sistema operacional, ou o código da falha, e e.message() é o texto. Error.saying(text) cria um para uma falha do próprio programa.
Métodos
is_some, is_none, unwrap, unwrap_or(d), is_ok, is_err, unwrap_err. Um match cobre o resto em duas linhas.
panic: o que não pode continuar
panic("index out of bounds");Um panic escreve PANIC: <what happened> na saída de erro padrão, e embaixo dele o lugar e a task: at main.talor:3:15, on main. Com TALOR_BACKTRACE=1 no ambiente ele também imprime os frames entre o panic e o início da task, e com TALOR_BACKTRACE=full, todos os frames.
Depois vem o unwind: cada frame entre o panic e o início da task libera aquilo de que é dono, e a task termina. Na main thread isso encerra o processo com o código 101. Em uma fiber ou em uma thread que o programa iniciou, só aquela task termina, e quem faz o join dela recebe Err com o código 101, o texto do panic, e e.panicked() verdadeiro: veja concorrência. Não existe catch.
O que causa panic: overflow e um shift além da largura em um debug build, um índice fora de um array, unwrap em None, uma alocação que o sistema operacional recusou, um lock tomado de novo pela task que já o segura, e panic(msg) escrito pelo programa. O código não é 1, porque 1 é o que qualquer processo responde quando falha por um motivo próprio, e um teste que espera um panic passaria com uma falha que não tem nada a ver.
Um stack overflow não é um panic: a guard page encerra o programa com o código de abort 134, porque não sobra stack para o unwind nem para imprimir.
never
O tipo de uma expressão que não retorna - panic, return, break - é never, que unifica com tudo: o ramo else de um if que produz valor pode ser um panic sem mudar o tipo.