Failure: status, not truth
ral keeps strictly apart two notions a shell conflates. Failure is a runtime
condition — a nonzero exit status or a raised error — that propagates
automatically until some form decides what to do with it. Truth is an ordinary
`Bool` value, inert data. ? and try react to failure;
if branches on truth; the two axes never cross.
A Bool is data, not a verdict. A ral predicate returns a Bool, and returning
false is a successful command: the value is recorded in the $status
register for inspection but raises nothing, so false never becomes a failure
and never drives ? or try. Branching on a Bool is if’s job — it rejects a
non-Bool condition as a type error; branching on success is try’s.
? is a fallback chain: the first success wins. a ? b ? c tries each arm
left to right, returns the first that does not fail, and skips the rest; only a
failure falls through to the next arm. A non-failure escape — exit, a
job-control stop, the tail-call trampoline — propagates out immediately instead
of being caught. If every arm fails the last error propagates, and all arms must
share one return type.
try turns failure into data, and is ral’s only ||. try B H runs B;
on success it returns B’s value, on failure it calls the handler H with an
error record [status, cmd, message, line, col] and returns its value,
unifying both outcomes into one type. It catches recoverable runtime errors and
nothing else — exit, job-control stops, and the trampoline bypass it. When a
command encodes its result in its exit status rather than a value — an external
grep -q, say — try is how that success drives a branch:
try { grep -q p f; echo found } { |_| echo missing }. There is no
command-level ||: it would force precedence against ? and | for a case
try already covers. The only || is the short-circuiting Bool connective
inside $[…] expression blocks (docs/SPEC.md §2), which takes strict Bool
operands and never inspects command success.
The cleanup forms differ only in what they do with the original failure:
trysuppresses it;guard B Cruns cleanupCand lets the failure keep propagating;- the prelude’s
attemptdiscards both result and failure.
Failure propagates predictably through the rest of the grammar:
- a sequence
a; b; chalts at the first failure; - a pipeline fails whole on any non-SIGPIPE stage failure;
for/mapstop iterating on a failing body;spawncaptures failure in the handle and surfaces it onawait;- an unhandled failure at top level exits the process with that status.
See also syscalls-are-effects (failure is an operation’s exceptional outcome),
pipelines,
control-operators, types,
cbpv.
Cite: RATIONALE §“Piping and failure”, §“No command-level ||”; docs/SPEC.md
§4.4, §10, §10.1–§10.2; eval_chain / eval_return in
core/src/evaluator/comp.rs, try / guard typing in
core/src/typecheck/scope.rs.