Ownership & safety
·
v0.0.13
Error handling without exceptions
There is no try, no catch, no throw, and no ? operator. A fallible function returns a tagged-union value and the caller matches on it. The control flow you see is the control flow that runs.
Define your own result type
enum ParseResult {
Ok(i32),
BadInput,
Overflow,
}
fn parse(s: str) -> ParseResult { ... }
The verbose form: explicit match
match is exhaustive, so you cannot forget a case:
fn parse_or_zero(s: str) -> i32 {
return match parse(s) {
ParseResult::Ok(v) => v,
ParseResult::BadInput => 0 -% 1,
ParseResult::Overflow => 0 -% 2,
};
}
The readable form: guard let
guard let binds the happy-path value and forces you to handle the failure by leaving the scope. The rest of the function then reads straight through:
fn handle(s: str) -> i32 {
guard let ParseResult::Ok(v) = parse(s) else { return 0 -% 1; };
return v +% 100;
}
Generic Result and Option from the stdlib
For the common shapes, the standard library provides generic types:
import "stdlib/result" as result;
import "stdlib/option" as option;
fn maybe_lookup(k: str) -> option::Option[i32] {
if k == "answer" { return option::Option[i32]::Some(42); }
return option::Option[i32]::None;
}
There is no ? propagation operator and no !T magic. The control-flow primitives plus guard let give you the same ergonomics with full locality: a reader never has to look anywhere but the function in front of them to see how an error travels.