Files
dusk/.claude/errors.md
T
2026-06-16 10:15:59 -05:00

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 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.