# Error Handling System Source: `src/dusk/error/` ## Philosophy Error handling is return-value based. Functions that can fail return `errorret_t`. There are no exceptions, `errno`, `setjmp`, or global error codes. Each thread has its own isolated error state. ## Types ```c typedef uint8_t errorcode_t; typedef struct { errorcode_t code; char_t *message; // allocated; freed by errorCatch char_t *lines; // call-stack trace; allocated; freed by errorCatch } errorstate_t; typedef struct { errorcode_t code; errorstate_t *state; // NULL on success } errorret_t; ``` **Constants:** `ERROR_OK = 0`, `ERROR_NOT_OK = 1`. Error state is thread-local: ```c extern THREAD_LOCAL errorstate_t ERROR_STATE; ``` Each thread has its own `ERROR_STATE` so concurrent errors never interfere. ## Macros ### Throwing an error ```c errorThrow("message %d", value); // Throws with ERROR_NOT_OK, captures __FILE__ / __func__ / __LINE__. errorThrowWithCode(code, "message %d", value); // Same but with a specific error code. ``` Both macros **return from the current function** with an `errorret_t`. Do not call them in void functions. ### Propagating up the call stack ```c errorChain(someCall()); ``` If `someCall()` returned an error, appends the current location to the stack trace and **returns** that error from the current function. If `someCall()` returned success, execution continues normally. ### Returning success ```c errorOk(); ``` Returns `errorret_t` with `code == ERROR_OK` and asserts the thread's `ERROR_STATE` is clean (no leftover error). Must be the last statement in a fallible function. ### Inspecting a result ```c if(errorIsOk(ret)) { ... } if(errorIsNotOk(ret)) { ... } ``` ### Cleaning up ```c errorCatch(ret); ``` Frees `ret.state->message` and `ret.state->lines`, resets the thread's `ERROR_STATE.code` to `ERROR_OK`. Safe to call on a success return (no-op). **Always call `errorCatch` on errors you are handling** -- otherwise the allocated message and stack-trace leak. ### Logging ```c errorPrint(ret); // prints code + message + stack trace, returns ret ``` ## Typical patterns ### Fallible function ```c errorret_t myFunction(int_t x) { if(x < 0) errorThrow("x must be non-negative, got %d", x); errorChain(someOtherFallibleCall(x)); errorOk(); } ``` ### Caller that handles errors ```c errorret_t ret = myFunction(-1); if(errorIsNotOk(ret)) { errorCatch(errorPrint(ret)); // ... fallback logic ... } ``` ### Stack trace accumulation Each `errorChain()` call appends a line to `ret.state->lines` in the format ` at file:line in function\n`. A deeply chained error produces a full call path readable from `ret.state->lines`. ## What NOT to do - Do not use raw `errno` for in-engine errors. - Do not return an error code integer -- always return `errorret_t`. - Do not ignore an error return without calling `errorCatch` on it if you are not propagating it. - Do not mix assertions with error handling. Assertions are for programmer mistakes; `errorThrow` is for expected failure paths.