error propagation
α1error propagation
ferrule provides lightweight sugar for working with Result<T, E>. all error handling is explicit. no hidden control flow, no exceptions.
construction
ok
wrap a success value:
return ok data;only valid in functions with fail<E> in their effects list.
err
construct an error:
return err NotFound { path: p };only valid in functions with fail<E> in their effects list. using ok or err without a fail<E> effect is a compile error.
propagation
check
unwrap a Result or return the error immediately:
const file = check fs.open(p);if fs.open returns err, check returns it from the current function. if it returns ok, check unwraps the value.
this is the workhorse of error handling. it's like rust's ? but more explicit.
check with context
add context frames when propagating:
const file = check fs.open(p) context { op: "open", path: p };context frames attach key/value pairs to the error for debugging. useful for understanding where errors came from.
note (α1): context frames are debug-only in α1. they're stripped in release builds. the syntax is available but context is only attached in debug mode. full context ledgers are α2.
guards
ensure
guard pattern for early error return:
ensure port >= 1 && port <= 65535 else err Invalid { message: "port out of range" };
ensure capability.granted(fs) == true else err Denied { path: p };if the condition is false, the error is returned. this replaces the pattern:
if port < 1 || port > 65535 {
return err Invalid { message: "port out of range" };
}complete example
use error IoError;
function read_file(p: Path, cap fs: Fs) -> Bytes effects [fs, fail<IoError>] {
ensure capability.granted(fs) == true else err Denied { path: p };
const file = check fs.open(p) context { op: "open" };
const data = check fs.read_all(file) context { op: "read_all" };
return ok data;
}what's planned
check ... map (α2) for error transformation:
const bytes = check read_file(p) map (e) -> ClientError { ClientError.File { cause: e } };this lets you convert between error domains while preserving context. replaces RFC-0001's map_error.
check ... context (α2) for adding debug context:
const file = check fs.open(p) context { op: "open", request_id: rid };replaces RFC-0002's with_context. context frames attach structured key/value data for debugging.
context ledgers (α2) for request-scoped context:
with context { request_id: rid, user_id: uid } in {
const resp = fetch(url, deadline);
// all errors inside this block have request_id and user_id attached
}ledgers automatically attach to all err/check within the scope.
summary
| syntax | purpose |
|---|---|
ok value | construct success |
err Variant { ... } | construct error |
check expr | unwrap or propagate |
check expr context { ... } | unwrap or propagate with context |
check expr map (e) -> E { ... } | unwrap or transform error (α2) |
ensure cond else err ... | guard with early return |