Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, *out is 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):

RangeOriginExamples
0successAIR_STATUS_OK
1..=13mirror of AirErrorKind (layer 1)AIR_STATUS_NOT_FOUND, AIR_STATUS_INVALID_DATA, AIR_STATUS_IO
100, 101C-boundary specificAIR_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 was NULL. Detected by upfront validation, without dereferencing: passing NULL never segfaults on Air’s side, you just get this status. (The release functions air_log_close/air_log_fields_free are an exception: NULL there 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 via panic;
  • you have no catch to write on the C side, and no AIR_PANIC to handle: the contract is “either AirStatus, 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.