Error Handling
Returned error values
Prefer standard-library result enums when using the standard library.
std.AccessResult<T> is the precise result of checked vector, buffer, and string access operations: its
variants are Ok(T) and OutOfBounds(IndexOutOfBoundsError). std.Result<T>
combines standard error categories for functions that call several APIs.
Both are defined in stdlib/result.sun; the compiler does not identify them by name.
_Result<T, E> remains builtin and needs no import. Its Ok payload contains
the successful value, and its Err payload contains the concrete error.
All these results follow ordinary enum ownership and cleanup rules, storing
the active payload inline without allocating exception storage.
/** Describes invalid port numbers. */
enum PortError { OutOfRange(i32) }
/** Validates a port without allocating. */
function validate_port(port: i32) _Result<i32, PortError> {
if (port < 1 or port > 65535) {
return _Result.Err(PortError.OutOfRange(port));
}
return _Result.Ok(port);
}
/** Propagates an invalid port to its caller. */
function configure_port(port: i32) _Result<i32, PortError> {
var valid = try validate_port(port);
return _Result.Ok(valid);
}A plain call returns a result for storage, forwarding, or handling with match:
var port = match validate_port(input) {
_Result.Ok(value) => value,
_Result.Err(error) => 8080
};Builtin and standard results cannot be silently discarded, including by storing it in an unused
local. Use ignore_error(operation()) to explicitly discard a result; this drops
its owned success or error payload.
Prefix try works with any nonempty enum whose first declared variant has
zero or one payload and whose remaining variants each have one payload. The
first variant represents success: try extracts its payload, or produces
void when it has none. Variant names and modules do not affect this rule.
Declaration order is therefore part of an enum's public contract.
/** Returns a value or a bounds error. */
enum AccessResult<T> {
Value(T),
OutOfBounds(IndexOutOfBoundsError)
}try evaluates its operand once. For a failure, it moves the payload into a
compatible failure variant of the enclosing function's returned enum and cleans
up live owners. When both enums are the same type, the original failure variant
is preserved. Otherwise, each possible failure must have exactly one destination
failure variant with the same payload type. The destination's first variant is
never an error destination. Missing or ambiguous conversions are compile errors,
even if a particular call happens to return success.
For compatibility with explicit error sums such as _Result<T, ErrorKind>, a
payload may also be wrapped in a unique immediate variant of the destination
error payload enum when no direct destination matches. This does not search
recursively through nested enums.
Calls, indexing, and member access belong to the operand; binary operators do
not: try read_value() + 1 means (try read_value()) + 1. In a generic enum, a
sole first-variant payload specialized to void becomes a unit success variant.
_Result<void, E> represents success without a value, written _Result.Ok.
try operation(); propagates such an operation. A successful reference such as
_Result<ref T, E> stays a borrow after propagation; its owner must remain alive.
A fallible constructor can declare init(...) AccessResult<void>. Calling the
class returns AccessResult<Class>, so var object = try ClassName(args);
propagates a construction error. Any generic result enum can be used when its
first type parameter holds the success value and its failure payload types do
not depend on that parameter. Additional type parameters are preserved.
Reaching the end of init, return;, and return AccessResult.Ok; all report
success. Use return AccessResult.OutOfBounds(error); for failure. The first
variant determines success; it need not be named Ok. Each failure variant
must have one payload. Every successful path must initialize every field;
failure drops initialized fields without calling deinit on the incomplete
object. The unsafe _init<T> operation returns the constructor's original
status type, such as AccessResult<void>, for explicit handling. Builtin
_Result<void, E> constructors remain supported.
Errors may also be borrowed explicitly, including through interface payloads:
/** Borrows errors whose storage belongs to another owner. */
enum BorrowedResult {
Ok,
RetryableError(const ref IRetryableError),
OtherError(const ref IError)
}The owner must outlive every use of the borrowed result. A function cannot return
an error view pointing into its own local storage. Custom enums support both match and prefix try when their variants satisfy
the declaration-order rule above.
See typed match bindings for handling several concrete payload types through one interface pattern.
Checked integer arithmetic
checked_div(a, b) and checked_rem(a, b) return
_Result<T, ArithmeticError>, where T is the operands' promoted integer type.
They evaluate each operand once and return an error for a zero divisor or
signed minimum divided by -1 (including remainder).
function quotient(a: i32, b: i32) _Result<i32, ArithmeticError> {
var value = try checked_div(a, b);
return _Result.Ok(value);
}Error messages
IError.code() and IError.message() are const methods. With the standard
library, message() returns an owned String copy that may outlive the error.
Without the standard library, the existing static_ptr<u8> contract is retained.
Error Types
An error is a class that implements the builtin IError interface:
interface IError {
const method code() i32;
const method message() String;
}Define your own by implementing both methods. Because an error is an ordinary class, it can carry whatever fields and extra methods you need:
class ParseError implements IError {
var line_: i32;
init(line: i32) { this.line_ = line; }
const method code() i32 { return 1; }
const method message() String { return String("bad input"); }
method line() i32 { return this.line_; }
}The standard library (using std;) provides Error(code, message) plus a set
of ready-made ones: EmptyError, NotFoundError, IndexOutOfBoundsError,
DivisionByZeroError, OverflowError, InvalidArgumentError, IOError,
AllocationError and ConversionError. Error accepts its message as a literal or as a String
built at runtime, and keeps its own copy.
Standard-library results
Fallible library calls return their concrete error types:
| Operation | Error type |
|---|---|
Vec.get, Vec.set, checked buffer indexing | IndexOutOfBoundsError |
Map.get, Map.remove | NotFoundError |
LinkedList.pop_front, String.pop | EmptyError |
safe_convert | ConversionError |
| JSON readers and parsing | JsonError |
| Protocol-buffer reads | ProtoDecodeError |
| File, process, environment, and socket operations | Error |
| Test assertions | std.test.AssertionError |
For a function that returns _Result<void, Error>, standalone propagation needs
no grouping parentheses:
try std.io.make_dir(path, 493);
return _Result.Ok;Handle an error without binding its payload with _Result.Err(_). To inspect
several concrete error payloads through one interface, use a typed borrowed arm:
match operation() {
_Result.Ok(value) => use(value),
(error: const ref IError) => println(error.message())
};Typed arms inspect immediate payloads. If E is itself an enum of error classes,
first match _Result.Err(error), then match error with the interface arm.
Built-in Arithmetic Errors
ArithmeticError implements IError and is available without an import.
checked_div and checked_rem return it on failure; ordinary integer
arithmetic traps instead.
/** Divides two integers, returning recoverable arithmetic failures. */
function divide(a: i32, b: i32) _Result<i32, ArithmeticError> {
return checked_div(a, b);
}
/** Handles division by zero through the error interface. */
function main() i32 {
return match divide(1, 0) {
_Result.Ok(value) => value,
(error: const ref IError) => error.code()
};
}| Failure | code() | message() |
|---|---|---|
| Zero divisor | 4 | integer division by zero |
Signed minimum divided by -1, including remainder | 5 | integer division overflow |
message() returns an owned String with the standard library, retaining the
existing static_ptr<u8> contract without it. Prefix try drops live owners
before returning a failure to its caller. A trap terminates execution without
running cleanup.
Cleanup during propagation
Prefix try returns early on failure and drops live owners before returning.
Code before the failing call has run; code after it has not. An owned error moves
through _Result.Err without copying its payload or allocating exception storage.
A borrowed error never transfers ownership of its referent.
Replacing Exception Syntax
The standard library, TLS APIs, generated protocol-buffer decoders, and test
runner use returned results. throws, throw, and block-form try/catch are
rejected with migration diagnostics. Declare a result return type, return an
error variant, and use try expression for propagation or match for handling.
Builtin _Result<T, E> remains available. Prefer library-defined result enums
with concrete owned errors; borrowed interface payloads are supported when
their referents outlive the result.