Error handling
Declare, throw, and handle errors.
On this page
A function that can fail declares an error type, and every caller must decide what to do when it fails: use a fallback, run a handler, or pass the error on.
Declare and throw an error#
error QuantityError (not_positive, too_large)
fn checked_quantity(value: int) int !QuantityError {
if value <= 0 : throw .not_positive
if value > 100 : throw .too_large
return value
}
fn main() {
println(checked_quantity(5) !? 1) // 5
println(checked_quantity(-2) !? 1) // 1
}
error Name (codes) lists the possible error codes. !QuantityError after the result type says the function may throw one; throw .code leaves the function with that error.
Handle an error#
| Form | On failure |
|---|---|
call() !? value |
Use a fallback value |
call() ! { ... } |
Run a handler |
call() ! return |
Run one statement |
call() !> |
Pass the error to your caller |
call() !! |
Panic with the error |
call() _ |
Ignore the error (and the result) |
Use !! only when failure means a bug, and _ only when the outcome really does not matter.
Fall back to a value#
fn main() {
let port = "abc".to(int) !? 8080
println(port) // 8080
let retries = "x".to(int) !? <{
println("Invalid retries, using 3")
return 3
}
println(retries)
}
A <{ ... } block runs several statements before giving the fallback. !? binds tighter than comparisons, so count() !? 0 == 3 means (count() !? 0) == 3.
Run a handler#
fn main() {
let quantity = "many".to(int) ! {
println("Enter a whole number, such as 3.")
return
}
println("Quantity: %quantity")
}
When the call's value is used, the handler must leave the flow: return, throw, break, continue or panic.
Pass an error to the caller#
fn doubled_input(text: String) int !SyntaxError {
let value = text.to(int) !>
return value * 2
}
fn main() {
let result = doubled_input("21") ! {
println("Could not parse the input.")
return
}
println(result) // 42
}
!> returns the same error, code and payload, from the current function. Its own error type must include that error.
Check which error happened#
Inside a handler, E is the error and E.code its code:
error LoadError (missing, denied)
fn load(name: String) String !LoadError {
if name == "secret" : throw .denied
throw .missing
}
fn main() {
load("secret") ! {
match E.code {
E.missing => println("not found")
E.denied => println("access denied")
}
}
load("x") ! {
if E.code == E.missing : println("missing")
println("code: %{E.code}") // code: missing
}
}
An error code converts to its name as text. error_is(E.code, missing, denied) checks for any of several codes.
Attach details to an error#
A payload carries data along with the code:
error NameError (empty, too_long) payload {
message: String
limit: uint (0)
}
fn check_name(name: String) !NameError {
if name.is_empty() : throw .empty { message: "A name is required." }
if name.length > 10 : throw .too_long { message: "Too long.", limit: 10 }
}
fn main() {
check_name("") ! println(E.message)
check_name("Bartholomew the Great") ! println("%{E.message} Max %{E.limit}")
}
Payload fields without a default must be set on every throw.
Combine error types#
extends includes the codes of other error types, so a function can pass their errors on:
error ConfigError (missing) extends (SyntaxError)
fn read_port(text: String) int !ConfigError {
if text.is_empty() : throw .missing
return text.to(int) !> // passes SyntaxError.syntax
}
fn main() {
read_port("") ! println("code: %{E.code}") // code: missing
read_port("x") ! println("code: %{E.code}") // code: syntax
}
Built-in error types#
| Type | Codes | Used for |
|---|---|---|
AnError |
error |
A generic failure |
LookupError |
missing, exists, range, empty |
Finding or storing something, such as Array.get |
SyntaxError |
syntax |
Parsing text, such as "x".to(int) |
SystemError |
failed, unsupported |
The OS could not answer |
IterError |
end |
Ending an each over a custom type |
InitError |
init |
Setup that failed |
Reuse error construction#
A function marked $throw must always throw. Call it where a throw would go:
error ParseError (invalid) payload {
message: String
line: uint
}
fn parse_error(message: String, line: uint) !ParseError $throw {
throw .invalid { message: message, line: line }
}
fn parse_line(text: String, line: uint) int !ParseError {
if text.is_empty() : parse_error("empty line", line)
return text.to(int) ! parse_error("not a number", line)
}
fn main() {
parse_line("", 3) ! println("%{E.message} on line %{E.line}") // empty line on line 3
}
Panic#
panic("message") stops the program with exit code 1 and prints the message with the file and line. Build with -d to get a full stack trace, see Debugging.
Errors are for calls that can fail. A value that may be missing is nullable instead and uses ??.

