Errors
Errors are values in Talor: there is no exception, no try/catch, no null. Absence is one variant, failure is another, and both flow through the type system.
Option<T>: absence
let a: Option<i64> = Some(3);
let b: i64? = None; // T? is sugar for Option<T>Result<T, E>: failure
fn parse(s: string): Result<i64, string> {
if s == "" { return Err("empty"); }
Ok(42)
}Option<T> is Some(T) | None; Result<T, E> is Ok(T) | Err(E). Both are read by match like any variant.
?: propagate
let v = parse(text)?; // returns Err(e) from the enclosing function on Err
let w = a?; // returns None from an Option-returning function? works on Option<T> inside a function returning Option<_>, and on Result<T, E1> inside a function returning Result<_, E2>. An Err leaves by the first of these that applies:
E1isE2: the error leaves as it is.- The program declares
impl Convert<E2> for E1(Convertis instd.error): the error leaves ase.convert(). E2is a variant with exactly one case that holds anE1and nothing else: the error leaves in that case.
Anything else is refused at the ?. A function whose body uses ? writes its return type.
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
The standard library's failures answer std.error's Error: e.code() is the operating system's error number, or the code of the failure, and e.message() is the text. Error.saying(text) makes one for a program's own failure.
Methods
is_some, is_none, unwrap, unwrap_or(d), is_ok, is_err, unwrap_err. A match covers the rest in two lines.
panic: what cannot continue
panic("index out of bounds");A panic writes PANIC: <what happened> on standard error, and under it the place and the task: at main.talor:3:15, on main. With TALOR_BACKTRACE=1 in the environment it also prints the frames between the panic and the start of the task, and with TALOR_BACKTRACE=full every frame.
Then it unwinds: every frame between the panic and the start of the task releases what it owns, and the task ends. On the main thread that ends the process with the code 101. In a fiber or a thread the program started, only that task ends, and whoever joins it gets Err with code 101, the panic's text, and e.panicked() true: see concurrency. There is no catch.
What panics: overflow and a shift past the width in a debug build, an index outside an array, unwrap on None, an allocation the operating system refused, a lock taken again by the task that holds it, and panic(msg) written by the program. The code is not 1, because 1 is what any process answers when it fails for a reason of its own, and a test that expects a panic would pass on an unrelated failure.
A stack overflow is not a panic: the guard page ends the program with the abort code 134, because there is no stack left to unwind or print from.
never
The type of an expression that does not return - panic, return, break - is never, which unifies with everything: the else branch of a value-producing if can be a panic without changing the type.