Fluxon Docs
Guide

Error handling

The `!` propagate operator, `??` for nil, `fail` to raise, and try/catch to recover.

In Fluxon a function can return success (ok) or an error (err). The one primary way to work with errors is the ! operator, and ?? for nil.

! — automatically propagate the error upward

If you put ! after a function name: if it returns an error, the error is automatically propagated to the caller (you do not check it by hand). If it succeeds, you get the result:

fn process id
  user = db.one "select * from users where id=$1" [id]!
  # if db.one returns an error, process also returns that error —
  # the next line never runs
  log user.name

This shrinks the multi-line if err != nil { return err } pattern into a single character.

?? — an alternative if nil

If a value is nil (not an error, just empty), provide an alternative with ??:

name = user.name ?? "guest"
each it in items
  p = db.one "...price..." [it.product]
  p ?? (ask_owner "Price?"; skip)    # if p is nil — ask and skip
  log p.price

fail — raise an error

Raise an error from your own code:

if qty < 1
  fail "invalid quantity"

fail with a status code — for expected errors. If you give fail a status code inside an HTTP handler, it automatically turns into a response with that status. This replaces try/catch: for an expected error, instead of deep nesting just fail:

http.on :post "/transfer" \req ->
  acc = db.one "select * from accounts where id=$1" [req.body.from]
  if acc.balance < req.body.amount
    fail 422 "insufficient balance"     # → 422 {error:"insufficient balance"} to the client
  # ... the main path, no nesting
  • fail 4xx "message" — an expected (business) error → a JSON response with that status.
  • fail "message" (no status) — an unexpected error → 500.

try / catch — catch an error

Often propagating the error (!) or turning it into an HTTP response (fail 4xx) is enough. But sometimes you must catch the error and keep going — give a default when an external API is down, retry, or log the error and continue the request. That is what try/catch is for:

user = try
  api.get "https://..."!        # an error here?
catch e
  log "api down: ${e.message}"     # e = {message, status}
  cached_user                       # → value of the catch body
  • catch ee binds to a {message, status} map. status is the fail status code, or nil for a status-less fail or a runtime error.
  • catch (no variable) — ignores the error.
  • Like if, try/catch is an expression: it yields the try body value on success, the catch body value on error.
  • ret/skip/stop are control flow, not errors: they pass through try uncaught.
  • Re-raise from inside catch with fail.

Canonical

! = propagate the error, ?? = replace nil, fail = raise an error (with or without a status), try/catch = catch and continue. For expected request errors prefer fail 4xx (the code stays flat); reach for try/catch only when you must recover and continue.

On this page