Errors
Async functions use the same pass / fail terminator
contract as sync functions. The difference is what they resolve
to: a sync pass v returns v to the caller; an async pass v
constructs a success Result<T> and stores it to the coroutine
promise, where the awaiter picks it up.
pass and fail in async bodies
async func:get_value = int32() {
pass 42i32; // success → Result<int32>{ value=42, is_error=false }
};
async func:get_error = int32() {
fail 99i32; // error → Result<int32>{ error=99, is_error=true }
};
Both pass and fail short-circuit the rest of the body. Code
after the terminator does not execute. This is the v0.31.0.3 D-7
contract:
async func:short_circuit = int32() {
fail 7i32;
println("DEAD"); // unreachable; compiler warns [dead-code]
pass 0i32; // unreachable
};
Regressions bug339–bug342 cover:
bug339—failmid-body, no DEAD marker reaches stdout.bug340—passmid-body, no DEAD marker.bug341—failinside awhilebody (the post-while statements are conservatively not warned dead, but they still do not execute when the loop body fails on iteration 1).bug342—failinside a nestedif, no DEAD marker.
The compiler emits a [dead-code] warning on the unambiguously
unreachable cases (bug339 / bug340 / bug342). The
while-body case (bug341) is conservatively reachable from
the post-loop point of view and is not warned.
failsafe is not invoked for fail inside async
func:failsafe = int32(tbb32:err) {
println("FAILSAFE"); // does NOT fire for the inner fail below
exit 1;
};
async func:inner = int32() {
fail 7i32; // resolves the future, does NOT escape to failsafe
};
async func:caller = int32() {
Result<int32>:r = await inner();
if (r.is_error == true) {
println("CAUGHT-VIA-RESULT");
}
pass 0i32;
};
A fail inside an async func: body produces an error Result<T>
at the awaiter. It does not propagate up to main's
failsafe. The error is delivered to the code that wrote
await inner().
This is intentional and matches the v0.21.x error-handling
philosophy: errors are values, not control-flow exceptions.
Result<T> carries the error; failsafe only triggers for
errors that reach the top of main synchronously.
If you want a fail deep in an async chain to terminate the
program, propagate it explicitly through each await:
async func:deep = int32() { fail 7i32; };
async func:mid = int32() {
int32:v = raw await deep(); // `raw` traps on is_error → terminates
pass v;
};
Or unwrap with ?:
async func:mid = int32() {
int32:v = (await deep())?; // `?` propagates fail to the caller's Result
pass v;
};
await outside async: ARIA-040
Since v0.31.0.1 (D-3), await outside an async func: body is
a hard compile-time error:
func:main = int32() {
int32:v = raw await get_value(); // ERROR
exit v;
};
ARIA-040: 'await' may only appear inside an 'async func:' body
Before v0.31.0.1, this was a soft-fail (stderr + exit 0).
Code in the wild that relied on the old behaviour will now
stop compiling — by design. The fix is to either make the
caller async (and arrange for it to be drop-spawned or
await-chained), or to drop-spawn the call and synchronise
some other way.
Regression: bug334 (must fail with ARIA-040); bug335 /
bug336 cover the executor-drain interaction (the success
side).
No try / catch
catch was tokenised in earlier cycles and was already rejected
by the v0.21.1 A-014 diagnostic with a redirect to
defaults / ? / !! / pick. The v0.31 PLAN proposed bringing
it back as an await-site error handler. The v0.31.0.0 audit
(D-8) recommended Option A — pull catch from the language
surface entirely — and v0.31.0.2 shipped that decision.
After v0.31.0.2:
TOKEN_KW_CATCHis removed fromtoken.h, both lexer keyword maps, and the token printer.catchis a plain identifier now. You may use it as a variable name.try { ... } catch { ... }still fails — but via a generic parser error on thetryblock shape, not via a dedicatedcatchdiagnostic. (tryis itself not a Nitpick keyword; the construct simply has no parse.)
Regressions: bug337 (catch as identifier compiles and runs;
exit code 7), bug338 (try/catch still rejected, just via
the generic path). The pre-existing bug088 / bug098
diagnostics still mention catch in their error messages —
they get it via the source-context line, not a dedicated
keyword check.
The substitutes:
| You wanted… | Use |
|---|---|
| Fallback value on error | defaults |
| Propagate error to caller | ? |
| Trap on error (terminate) | raw or !! |
| Structured error handling | pick err.code { ... } |
| Capture error and continue | Result<T>:r = await x; if (r.is_error == true) { ... } |