Ship

Old Docs · Errors And Fallible Functions
On this page · 12 sections

Errors And Fallible Functions

Decisions

  • No Result(T, E) ADT for ordinary fallible control flow.

  • Fallible functions carry a structural exception row in their embedded Call, written after the return type as comma exn clauses.

  • Produce with error :tag(payload); (separate from return).

  • Error sets are open variants of lowercase colon tags (compose across callees). Fixed domain data still uses closed variants.

  • Auto-propagate by default — including inside higher-order callbacks. No per-call propagation operator. Bindings are plain success types; exception rows live on the function's embedded Call via comma exn clauses.

  • Omit exn in .shp → infer. .shc contracts must use fn! or one-or-more exn clauses (or #infer).

  • fn! seals totality (exn = []). A fallible body is a compile error.

  • match / try pause auto-propagate; listed tags subtract; unlisted propagate. match uses | error :tag -> ...; try is error-only (| :tag -> ...).

  • Fallible defer and provider drop share the same channel; With.execute is total, so a with release fails only as a defect. Body + cleanup failures → one flat Cause.

  • Validated construction (ensure) is fallible unless comptime-proven. No unvalidated bypass.

  • must expr converts expected errors into defects (uncatchable by match/try).

  • error rest / error _ allowed for open residuals; not the usual style.

  • Impossible error arms are compile errors.

  • Option(T) = presence. ?> = presence fallback via canonical OrElse (does not catch the left operand's error channel).

  • Expected exceptions are structural and typed; panics/defects remain unexpected and outside exn.

Function Shape

let read_config = fn(path: Path) -> Config {
  if (Fs.exists(path) == False) {
    error :missing_config(path);
  }
  let text = Fs.read(path);
  return Config.parse(text);
};
// inferred: -> Config,
//   exn [ :missing_config(Path) | :io(...) | :parse(...) ]
let handle_create = fn(req: Request) -> Response {
  let body = read_body(req);
  let request = Json.decode(body, CreateTodoRequest);
  let item = Todo.create(request.title, Clock.now());
  return Response.ok(TodoResponse { item });
};

Auto-Propagate

let create = fn(title: String, now: Instant) -> Todo {
  let title = NonEmptyString.parse(title);
  return {
    id = TodoId.new();
    title;
    status = Open;
    created_at = now;
    updated_at = now;
  };
};

let double = pure fn!(x: Int) -> Int {
  return x * 2;
};
// fallible subexpression inside `fn!` → compile error

Higher-order

Fallible closures inherit the enclosing exception context. A bare callback parameter is relative Current flow, so its concrete exn joins the call expression. APIs that retain or transform whole callable signatures use explicit sig and row algebra.

Handling Contexts

let user = match load(id) {
  | u -> u
  | error :not_found(_) -> User.guest()
  | error :db(e) -> {
    log(e);
    error :service_down;
  }
};

let title = try NonEmptyString.parse(raw) {
  | :empty_title -> NonEmptyString.unsafe("Untitled")
};

let x = load(id); does not store errors — success binds x: User; failure short-circuits. Storing fallible outcomes: open in ../questions/035-fallible-value-representation.md.

Cleanup Failures

let run = fn(path: Path) -> Unit {
  let file = File.open(path);
  defer File.close(file);
  process(file);
}
// inferred errors include open, process, and close

Body + cleanup failures → one flat Cause. Seal cleanup with fn! or handle locally only when intentional. See 14-resource-cleanup.md.

Validated Construction

type t = {
  age: Int #{
    ensure = fn(n) {
      if (n < 0) { error :negative_age(n); }
    };
  };
}

let user = { age = 37 };              // fallible; auto-propagates
let updated = { user with age = -1 }; // re-runs ensures

Comptime-evaluable field values run the same ensure during checking: success removes that field's tags at the site; failure is a compile error; fully proven sites skip runtime re-validation (not an unvalidated bypass). Per-field granularity; whole-product ensures prove only at complete comptime-evaluable literals. Defaults validated when the type is checked.

Must And ?>

let user = must { age = age };   // defect on failure: greppable, rare

let session =
  Cookie.read(request, "quotes_session") ?> error :unauthorized;
let theme = configured_theme ?> "system";

?> expands through #code / #insert + canonical OrElse. Errors from the left operand still auto-propagate; only successful None selects the fallback. See 11-behavior-modules.md and 06-runtime-and-implementation.md (Code).

Structural Exception Declarations

exception ParseFailure = [
  | :invalid_token(Token)
  | :unexpected_eof
]

exception TimedFailure(open 'errors) = [
  | 'errors
  | :timeout(Duration)
]

Name exception-row aliases *Failure; reserve *Error for structured payload data carried by a tag. For example, exception ParseFailure = [ :parse(ParseError) ] keeps the control-flow row distinct from a rich parse diagnostic record. When the payload is just text, use :parse(String)—do not invent an opaque type ParseError;.

exception declares a kinded structural row, not a nominal exception identity. Rows are exact unless opened through an explicit binder. Equal tags with equal payload shapes deduplicate across declarations; conflicting payload shapes are a type error.

Tags And Boundaries

  • Named structural declarations compose with inline tags: exn [ DecodeFailure | :timeout(Duration) ].

  • .shc: every function header uses fn! or one-or-more exn clauses (or #infer). A silent exception-axis default is a contract error.

  • .shp: infer from the body. No Result(...) spelling.

What Ship Does Not Do

RejectedInstead
Result / Ok / Err defaultError channel + auto-propagate
Per-call propagation operatorAuto-propagation
else error :tag chainsmaybe ?> error :tag
try ... catch :tag`try expr { \:tag -> ... }ormatch`
Unvalidated ensure bypassAlways validate (or comptime-prove)

01-syntax.md, 02-type-inference.md, book/003-Functions.shp, decisions.md, lexicon error-channel / error-row.

Open: multi-reason Cause diagnostics metadata; storing fallible outcomes; nested auto-propagate typing edge cases.