Errors and NULL discipline
The status channel
Section titled “The status channel”- Functions report success/failure with
corvid_status(CORVID_OK/CORVID_ERR), or with a NULL return where a handle/buffer was expected. - On failure, the detail is in thread-local storage:
corvid_last_error_code()— one of the 19 codes (0 = nothing failed on this thread);corvid_last_error_message(&len)— the engine’s human-readable text, NUL-terminated for convenience.
- Failure signals are always paired with a freshly recorded last error —
a
CORVID_ERRstatus or a failure-NULL sets the thread-local code and message as its first act. - Message lifetime: valid until the next failing corvid call on the same thread (or thread exit). Copy it if you need it longer. Successful calls do not clear the last error — read it immediately after the failure that interests you.
- Errors never leave partial side effects Rust would not allow — transactions
are atomic per call (a
CORVID_ERRfromcorvid_put_manymeans the whole batch rolled back). - The engine never panics on user input; the FFI additionally converts any
residual panic to
CORVID_ERR+ message (defensive, not contract).
Absence is a success
Section titled “Absence is a success”“Optional value” results — corvid_get, corvid_schema,
corvid_query_min, corvid_query_max — use an out-parameter plus
status: CORVID_OK + *out = value, or CORVID_OK + *out = NULL for
“no such value” (a missing document, an undeclared schema, no comparable
value). Absence is never an error and never signalled by a bare NULL return —
only unambiguous handles/buffers (open, run, constructors, auto-key) return
NULL for failure.
The NULL discipline (never UB)
Section titled “The NULL discipline (never UB)”- An unexpected NULL — NULL handle, NULL required out-param, NULL data
pointer with nonzero length — returns
CORVID_ERRwithCORVID_E_ARGUMENT. Never UB. - Non-status functions (no
corvid_statusreturn):corvid_value_type,corvid_value_as_bool/as_int/as_float,corvid_value_len, the_reftrio, and all five_nextcursors follow the same discipline through a defined inert value —0/*ok = 0/NULLpointer /0(= exhausted) — and recordCORVID_E_ARGUMENTin the thread-local last error. Never UB, never a status return. - Nullable-by-contract pointers carry semantics:
corvid_compare_and_set’sexpected/replacement(absent / delete),corvid_page’safter(start),corvid_update’scurrent/*out(absent / delete), optional out-params (existed_out,removed_out,moved_out, thelen_outs,doc_out). - Empty (pointer, length 0) is distinct from NULL and legal for keys, names, text, bytes, vectors — the engine accepts empty keys and documents.
corvid_free(NULL)and every_free(NULL)are no-ops.- UTF-8-requiring strings with invalid encoding:
CORVID_E_ARGUMENT(checked, copied, never UB).
The error codes
Section titled “The error codes”Codes 1–18 map 1:1 onto the engine’s corvid::Error variants; 19
(CORVID_E_BUSY) is FFI-only (compact-while-derived-handles-open). The full
frozen table with meanings is the generated
error codes reference. The mapping is pinned by a
variant-inventory snapshot test — adding, removing, or renaming an engine
variant fails the FFI suite until the mapping is maintained; new variants
append code 20+, never fill a gap.