Error model (AirStatus)
Air returns errors in-band: the return value carries the status. No errno, no
exceptions, no panic crosses the ABI. This page explains how to check,
read, and propagate these statuses — and why this model was chosen
(ADR-045).
The basic rule
Almost every function returns an enum AirStatus. AIR_STATUS_OK == 0 is the only
success case, and the only case where an *out is written:
AirUuid u;
AirStatus st = air_uuid_new_v7(&u);
if (st != AIR_STATUS_OK) {
/* `u` was NOT written — do not read it. */
fprintf(stderr, "failed: %s\n", air_status_message(st));
return 1;
}
/* Only here is `u` valid. */
Pitfall. Never read an output parameter before checking for
AIR_STATUS_OK. On error,*outis left untouched (uninitialized if you didn’t initialize it). This is deliberate: zero partial writes.
Reading a status: air_status_message
const char *msg = air_status_message(st); /* never NULL */
The string is static (strerror-like, no locale, no global state): valid for the
whole program lifetime, must not be freed. It is a technical English message for
logs/diagnostics — not a localized end-user string.
The status catalogue
Two families, with committed discriminants (ADR-012):
| Range | Origin | Examples |
|---|---|---|
0 | success | AIR_STATUS_OK |
1..=13 | mirror of AirErrorKind (layer 1) | AIR_STATUS_NOT_FOUND, AIR_STATUS_INVALID_DATA, AIR_STATUS_IO… |
100, 101 | C-boundary specific | AIR_STATUS_NULL_ARGUMENT, AIR_STATUS_BUFFER_TOO_SMALL |
The two boundary statuses are the ones you can trigger by misusing the API:
AIR_STATUS_NULL_ARGUMENT(100) — a required pointer wasNULL. Detected by upfront validation, without dereferencing: passingNULLnever segfaults on Air’s side, you just get this status. (The release functionsair_log_close/air_log_fields_freeare an exception:NULLthere is a documented no-op, not an error.)AIR_STATUS_BUFFER_TOO_SMALL(101) — your output buffer is too small. Nothing is written (no silent truncation, ADR-032). Grow the buffer to the header constants and call again.
Why no errno?
errno is per-thread global state, and brittle: easy to clobber between the call
and its read, invisible in the signature, a source of classic C bugs. The in-band
status is, by contrast:
- explicit in the return type (impossible to ignore by accident);
- local (no shared state, so trivially thread-safe);
- stable (committed discriminants).
Why no panic crosses the ABI
The crate is built with panic = "abort". As a result, a Rust panic does not
propagate across the C boundary (that would be undefined behavior) — it aborts the
process immediately (fail-fast). In other words:
- a
panic= a bug in Air, never a normal error condition; - expected conditions (invalid input, missing resource, short buffer) are all
reported via
AirStatus, never viapanic; - you have no
catchto write on the C side, and noAIR_PANICto handle: the contract is “eitherAirStatus, or the process stops”.
This is a safety choice: a clean, diagnosable abort beats a stack unwind that would corrupt a C caller’s state.
Propagate cleanly
Since AirStatus is a value, propagation is trivial: relay the status, or map it onto
your own error model. Typical pattern:
AirStatus do_work(AirLog *log) {
AirInstant t0;
AirStatus st = air_instant_now(&t0);
if (st != AIR_STATUS_OK) {
return st; /* bubble up as-is */
}
/* … */
return AIR_STATUS_OK;
}
Version française : Modèle d’erreurs.