Skip to main content
Kairo draws a hard line between two different kinds of bad outcome. A failure is an expected, domain-meaningful result — validation rejects an input, a payment is declined, a record is not found. A fault is an unexpected invariant violation — something your program assumed could not happen, did happen. Result<T, E> is the tool for failures: it is a first-class value that your functions return and your callers handle explicitly. Faults are not Result values; they terminate the current execution boundary and signal that the program reached a state it was not designed to handle. Keeping these two categories separate means your domain logic stays clean, your error paths are visible, and unexpected failures never silently masquerade as normal outcomes.

Result<T, E> — Construction

Result<T, E> is part of the Core prelude. You do not need to import it. Construct a successful outcome with Result.success() and a failed outcome with Result.failure().
The type parameter E must be value-like — use an enum or a value type, not a raw Text or a numeric code. This ensures errors are structured, pattern-matchable, and self-documenting.

Inspecting a Result

A Result<T, E> exposes four members for inspection:
Accessing .value on a failed Result, or .error on a successful Result, causes a fault — an unexpected runtime error that terminates the current execution boundary. Always guard access with .isSuccess or .isFailure first.

Canonical Inspection with match

The standard way to handle a Result is to match on its .isSuccess property. This makes both branches visible and forces you to handle each case explicitly.
You can also use if when only one branch needs action:
Propagating a failure up the call stack is idiomatic in Kairo. If your function cannot recover from an inner failure, return Result.failure(...) immediately and let the caller decide what to do.

Designing Error Types

Error types communicate the full vocabulary of ways an operation can fail. Prefer enum when the set of failure cases is closed and each case is self-contained:
Use a value type when a failure case carries structured data that the caller needs to act on:
You can also combine the two — an enum whose cases carry value payloads (where the language supports associated values in a future version). For v0.1, use separate value types for cases that carry data and enum for those that do not.
Do not use raw Text or numeric codes as error types. Result.failure("something went wrong") is not meaningful to a caller. A typed error — ValidationError.nameTooLong — is pattern-matchable, documentable, and refactorable.

Faults

A fault is distinct from a Result failure. Where a failure is an expected outcome that your function deliberately returns, a fault is an unexpected invariant violation that Kairo detects at runtime and treats as an unrecoverable condition within the current execution boundary. Faults occur when your code does something it should never have done — for example:
  • Accessing .value on a failed Result
  • Accessing .error on a successful Result
  • Violating a precondition the runtime enforces
Faults are not catchable in the way exceptions are in other languages. They signal programmer error, not domain error. The correct response to a fault is to fix the code that caused it, not to add a handler around it.
Think of faults as the equivalent of null-pointer exceptions or index-out-of-bounds errors in other languages — they indicate that the program reached a state the developer did not account for. Result is for states you anticipate; faults are for states you should have prevented.

Full Example

Here is a complete example showing a function that validates a customer name, an error enum, and a caller that handles both outcomes:
And a caller that uses match to handle each case:
Keep error enums close to the module that produces them. NameValidationError lives in Customers.Validation — the module that knows all the ways a name can be invalid. Callers import the type when they need to inspect or propagate it.

Summary

Result failure

An expected, domain-meaningful outcome. Return Result.failure(error) to signal it. The caller inspects and handles it explicitly.

Fault

An unexpected invariant violation. Not a Result. Terminates the execution boundary. Fix the code that caused it — don’t try to catch it.

Error type design

Use enum for closed sets of failure cases. Use value when a case carries structured data. Never use raw Text or numbers.

Canonical inspection

Match on result.isSuccess. Access .value only when isSuccess is true. Access .error only when isSuccess is false.