ferrule

error domains

α1
standalone-error-typesdomain-syntaxdomain-compositionuse-error-defaultpick-omit (α2)inline-error-unions (α2)

error domains

ferrule uses errors as values. no exceptions, no implicit panics. errors are types you define and return.

standalone error types

define error types outside of domains:

error NotFound { path: Path }
error Denied { path: Path, reason: String }
error Timeout { ms: u64 }
error ParseFailed { line: u32, message: String }

standalone errors can be:

  • used directly in function signatures
  • combined into domains
  • reused across multiple domains

domains

domains group related errors into a union. two syntaxes:

union syntax (preferred)

reference standalone error types by name:

domain IoError = NotFound | Denied | Timeout;
domain ParseError = ParseFailed | UnexpectedToken;

use this when errors are reused across multiple domains.

inline variant syntax

define error variants directly:

domain IoError {
  NotFound { path: Path }
  Denied { path: Path, reason: String }
  Timeout { ms: u64 }
}

use this for domain-specific errors that won't be reused.

domain composition

compose domains using union syntax:

// standalone errors
error NotFound { path: Path }
error Denied { path: Path }
error Timeout { ms: u64 }
error ConnectionRefused { host: String }
error ParseFailed { line: u32 }

// domains as unions
domain IoError = NotFound | Denied;
domain NetError = Timeout | ConnectionRefused;
domain ParseError = ParseFailed;

// union of domains
domain AppError = IoError | NetError | ParseError;

// add extra errors to existing domain
domain ExtendedIoError = IoError | PermissionError;

module default error

use use error to set a default error domain for the module:

use error IoError;

// functions in this module default to error IoError
function readFile(p: Path, cap fs: Fs) -> Bytes effects [fs] {
  // implicitly: error IoError
}

public exports must be explicit about their error domain regardless of module defaults.

precise error signatures

use specific errors instead of full domains when appropriate:

// full domain
function process(input: Input) -> Output error AppError { ... }

// precise: only these two errors possible
function validate(input: Input) -> Output error (ParseFailed | InvalidFormat) { ... }

this helps callers know exactly what can fail.

complete example

// standalone error types
error NotFound { path: Path }
error Denied { path: Path }
error Timeout { ms: u64, operation: String }
error ConnectionRefused { host: String, port: u16 }
error ParseFailed { line: u32, column: u32, message: String }

// group into domains
domain IoError = NotFound | Denied | Timeout;
domain NetError = Timeout | ConnectionRefused;
domain ParseError = ParseFailed;

// compose domains
domain AppError = IoError | NetError | ParseError;

// function using composed domain
function loadConfig(path: Path, cap fs: Fs) -> Config error AppError effects [fs] {
  const bytes = check readFile(path, fs);  // IoError
  const parsed = check parse(bytes);        // ParseError
  return ok parsed;
}

// function with precise errors
function readOnly(path: Path, cap fs: Fs) -> Bytes error (NotFound | Denied) effects [fs] {
  // can only fail with NotFound or Denied, never Timeout
}

what's planned

pick/omit (α2) for extracting subsets:

// only NotFound and Denied
type ReadErrors = Pick<IoError, NotFound | Denied>;

// everything except Timeout
type FastIoError = Omit<IoError, Timeout>;

summary

syntaxpurpose
error Name { fields }standalone error type
domain D = E1 | E2;domain as union (preferred)
domain D { E1 { } E2 { } }domain with inline variants
domain D = D1 | D2;compose domains
error (E1 | E2)inline error union in signatures
use error D;module default domain

On this page