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.nameThis 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.pricefail — 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 nestingfail 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 bodycatch e—ebinds to a{message, status}map.statusis thefailstatus code, ornilfor a status-lessfailor a runtime error.catch(no variable) — ignores the error.- Like
if,try/catchis an expression: it yields thetrybody value on success, thecatchbody value on error. ret/skip/stopare control flow, not errors: they pass throughtryuncaught.- Re-raise from inside
catchwithfail.
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.