3.0 KiB
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
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:
extern THREAD_LOCAL errorstate_t ERROR_STATE;
Each thread has its own ERROR_STATE so concurrent errors never
interfere.
Macros
Throwing an error
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
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
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
if(errorIsOk(ret)) { ... }
if(errorIsNotOk(ret)) { ... }
Cleaning up
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
errorPrint(ret); // prints code + message + stack trace, returns ret
Typical patterns
Fallible function
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
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
errnofor in-engine errors. - Do not return an error code integer -- always return
errorret_t. - Do not ignore an error return without calling
errorCatchon it if you are not propagating it. - Do not mix assertions with error handling. Assertions are for
programmer mistakes;
errorThrowis for expected failure paths.