Banjo API 1.0.0-rc.2
Low-level C99 game development API
Loading...
Searching...
No Matches
Error Management

Macros

#define bj_error_code_kind(code)
#define bj_error_code_is_user(code)

Enumerations

enum  bj_error_code {
  BJ_ERROR_NONE = 0x00000000 , BJ_ERROR = 0x00000001 , BJ_ERROR_UNSUPPORTED = 0x00000101 , BJ_ERROR_NOT_IMPLEMENTED = 0x00000201 ,
  BJ_ERROR_SYSTEM = 0x00000002 , BJ_ERROR_FILE_NOT_FOUND = 0x00000102 , BJ_ERROR_CANNOT_ALLOCATE = 0x00000202 , BJ_ERROR_INITIALIZE = 0x00000302 ,
  BJ_ERROR_DISPOSE = 0x00000402 , BJ_ERROR_IO = 0x00000003 , BJ_ERROR_CANNOT_READ = 0x00000103 , BJ_ERROR_CANNOT_WRITE = 0x00000203 ,
  BJ_ERROR_INVALID_DATA = 0x00000004 , BJ_ERROR_INVALID_FORMAT = 0x00000104 , BJ_ERROR_INCORRECT_VALUE = 0x00000204 , BJ_ERROR_VIDEO = 0x00000005 ,
  BJ_ERROR_AUDIO = 0x00000006 , BJ_ERROR_NETWORK = 0x00000007 , BJ_ERROR_NETWORK_SOCKET = 0x00000107 , BJ_ERROR_NETWORK_TIMEOUT = 0x00000207
}

Functions

void bj_set_error (struct bj_error **error, uint32_t code, const char *message)
void bj_set_error_fmt (struct bj_error **error, uint32_t code, const char *format,...)
void bj_propagate_error (struct bj_error **dest, struct bj_error *src)
void bj_propagate_prefixed_error (struct bj_error **dest, struct bj_error *src, const char *format,...)
void bj_prefix_error (struct bj_error **error, const char *prefix)
void bj_prefix_error_fmt (struct bj_error **error, const char *format,...)
struct bj_errorbj_copy_error (const struct bj_error *error)
void bj_clear_error (struct bj_error **error)
bj_bool bj_error_matches (const struct bj_error *error, uint32_t code)
bj_bool bj_error_matches_kind (const struct bj_error *error, uint32_t kind)
uint32_t bj_error_code (const struct bj_error *error)
const char * bj_error_message (const struct bj_error *error)

Detailed Description

Report and inspect things that went wrong, without exceptions.

C doesn't have exceptions. When a function can fail, it has to tell the caller two things: that it failed, and what went wrong. Banjo's convention is that the function's normal return value carries the result (the value you asked for, or a sentinel like NULL if there's no useful value), and a separate out-parameter at the end of the argument list carries the error details.

"Out-parameter" just means a pointer the caller hands in for the function to fill: the caller owns the storage, the function writes into it. Concretely, every fallible Banjo function has this shape:

bj_error** error);
struct bj_bitmap * bj_create_bitmap_from_file(const char *path, struct bj_error **error)
Creates a new bitmap by loading from a file.
struct bj_error bj_error
Definition api.h:333
struct bj_bitmap bj_bitmap
Definition api.h:328

The return value (bj_bitmap*) is what the caller wanted. The trailing bj_error** is the error sink. The caller chooses how much they care:

  • Don't care: pass NULL. The call is free of allocation overhead even if the function fails.
  • Care: declare a bj_error* err = NULL, pass &err. If the function fails, err is non-NULL on return and carries a code and a human-readable message you can inspect.
  • Already have one: pass &existing_err. Banjo follows first-error-wins: the function logs its new failure but leaves your existing error untouched, so the root cause is never overwritten by cascading consequences.

This is the same pattern GLib's GError uses, if you've seen that. The recipes section below shows it in action.

The three pointer states

For a function declared bj_bool bj_do_thing(int arg, bj_error** error), the error parameter can be in one of three states, each meaningful:

State Meaning Function behaviour
error == NULL Caller does not want error details. Zero-cost path: no allocation; in debug builds the error is logged then dropped.
error != NULL and *error == NULL Caller wants to receive an error. On failure, allocates a bj_error and stores it into *error.
error != NULL and *error != NULL Caller already has an unhandled error. First error wins: new error logged then dropped; original preserved.

The first state is what makes the out-parameter cheap: passing NULL costs one pointer comparison. The third (first-error-wins) points at the root cause when several fallible calls appear without intermediate handling; later failures are usually cascading consequences.

Ownership and lifetime

An error returned through *error is caller-owned. Three functions move or destroy it:

  • bj_clear_error frees the error and NULLs the pointer; safe on NULL.
  • bj_propagate_error transfers ownership upward; after the call, src is consumed and you must not touch it.
  • bj_copy_error makes an independent duplicate (both must eventually be freed).

Errors discarded by first-error-wins or NULL passing are freed by the library itself; the caller doesn't need to do anything.

The 32-bit error code

Error codes are packed in three fields:

+----------+------------------+------------+
| Origin | Specific Code | Kind |
| (8 bits) | (16 bits) | (8 bits) |
+----------+------------------+------------+
MSB LSB
  • Kind (LSB) is the category: I/O, system, video, audio, network… Several specific codes share a kind.
  • Specific is the unique sub-code within a kind.
  • Origin (MSB) is 0x00 for Banjo errors, non-zero for user-defined.

For example, BJ_ERROR_INCORRECT_VALUE is 0x00000204: origin 0x00 (Banjo), specific 0x0002, kind 0x04 (same as BJ_ERROR_INVALID_DATA).

This gives two useful matching strategies:

  • Exact match via bj_error_matches when behaviour depends on a specific failure ("file not found → fall back to default path").
  • Kind match via bj_error_matches_kind when behaviour depends on a category ("any I/O failure → bail").

Applications can mint their own codes with a non-zero MSB without ever colliding with Banjo's reserved space. bj_error_code_is_user distinguishes them.

Recipes

Receive an error from a fallible call

int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
bj_info("=== Basic Error Handling ===");
// To receive error information, pass a pointer to a NULL bj_error*
bj_error* error = 0;
initialize_server("/etc/myapp.conf", 8080, &error);
if (error != 0) {
// The error message now includes context from the call chain
bj_err("Startup failed: %s", bj_error_message(error));
// Always clear errors when done to free memory
bj_clear_error(&error);
}
bj_info("\n=== Error Matching ===");
bj_info("\n=== Error Copying ===");
bj_info("\n=== Zero-Cost Path ===");
return 0;
}

Set an error from your own function

Take bj_error** as the last parameter. Use bj_set_error for literal messages and bj_set_error_fmt for formatted ones (don't snprintf into a buffer and then bj_set_error, that doubles the work and trips CERT-ERR33-C):

// Functions that can fail take a bj_error** as their last parameter.
// Use bj_set_error for literal messages.
void load_config_file(const char* path, bj_error** error) {
(void)path;
// Simulate a failure
int file_exists = 0;
if (!file_exists) {
bj_set_error(error, BJ_ERROR_FILE_NOT_FOUND, "configuration file missing");
return;
}
// ... normal processing ...
}
// Use bj_set_error_fmt for formatted messages with runtime values.
void open_network_port(int port, bj_error** error) {
// Simulate a failure
int port_available = 0;
if (!port_available) {
"port %d is already in use", port);
return;
}
// ... normal processing ...
}

Propagate up the stack

bj_error* local_err = NULL;
do_something(&local_err);
if (local_err != NULL) {
bj_propagate_error(error, local_err); // local_err is now consumed
return BJ_FALSE;
}
#define BJ_FALSE
Boolean false value (0).
Definition api.h:266
void bj_propagate_error(struct bj_error **dest, struct bj_error *src)
Propagates an error to the caller's error location.

Propagate with context (one call)

bj_error* local_err = NULL;
load_config_file(path, &local_err);
if (local_err != NULL) {
bj_propagate_prefixed_error(error, local_err,
"While initializing server: ");
return;
}
void bj_propagate_prefixed_error(struct bj_error **dest, struct bj_error *src, const char *format,...)
Propagates an error with an added prefix.
void load_config_file(const char *path, bj_error **error)

A chain of these produces a top-down readable message: "While initializing server: While loading config: file not found".

Match by exact code or kind

// Retry with a fallback path
}
// Any I/O failure: log and bail
}
bj_bool bj_error_matches_kind(const struct bj_error *error, uint32_t kind)
Checks if an error belongs to a specific error kind (category).
bj_bool bj_error_matches(const struct bj_error *error, uint32_t code)
Checks if an error matches a specific error code.
@ BJ_ERROR_FILE_NOT_FOUND
Requested file was not found.
Definition error.h:265
@ BJ_ERROR_IO
Generic I/O error.
Definition error.h:275

Both functions accept NULL and return false, so you can use them without a separate null check.

Mint your own error codes

#define MYAPP_ERROR_BAD_CONFIG 0x01010104 // origin 0x01, specific 0x0101, kind 0x04

bj_error_code_is_user(code) returns non-zero exactly when the MSB is non-zero; kind-matching still works because the kind stays in the LSB.

Common pitfalls

  • Forgetting to clear. Errors returned through *error are caller-owned. If you don't bj_clear_error, the bytes leak. The test harness's mock_allocators will catch this.
  • Using a propagated error. After bj_propagate_error or bj_propagate_prefixed_error, the source error is consumed. Touching it afterwards is undefined behaviour.
  • Cascading-error noise. If several fallible calls run in sequence without intermediate handling, only the first failure is captured (first error wins). That's a feature: it points at the root cause, not the consequences.
  • Discarding return values with (void). CERT-ERR33-C forbids this on any function that returns a status. Banjo's CI runs clang-tidy with cert checks as warnings-as-errors. Fold the return value into your control flow:

    // Wrong:
    (void)bj_set_error_fmt(&err, BJ_ERROR_IO, "bad: %s", path);
    // Right:
    bj_set_error_fmt(&err, BJ_ERROR_IO, "bad: %s", path);
    return BJ_FALSE;
    void bj_set_error_fmt(struct bj_error **error, uint32_t code, const char *format,...)
    Creates a new error with a formatted message.

Defined error kinds

From the bj_error_code enum:

Kind code Mnemonic Covers
0x01 BJ_ERROR General / unspecified / unsupported / not-implemented
0x02 BJ_ERROR_SYSTEM OS-level failures (alloc, init, dispose, file-not-found)
0x03 BJ_ERROR_IO Read / write failures
0x04 BJ_ERROR_INVALID_DATA Bad format, incorrect value
0x05 BJ_ERROR_VIDEO Video subsystem (see Windows)
0x06 BJ_ERROR_AUDIO Audio subsystem (see Audio)
0x07 BJ_ERROR_NETWORK Network subsystem (incl. socket and timeout)

BJ_ERROR_INITIALIZE (0x00000302) is the code you'll see when bj_begin_video or bj_begin_audio fail with "no suitable video" / "no suitable audio"; its kind is BJ_ERROR_SYSTEM, so a catch-all bj_error_matches_kind(err, BJ_ERROR_SYSTEM) catches it alongside file and allocation failures.

See also
The runnable example handling_errors.c walks through every recipe above.

Macro Definition Documentation

◆ bj_error_code_is_user

#define bj_error_code_is_user ( code)
Value:
(((code) >> 24) & 0xFFu)

Checks if an error code is user-defined (not from Banjo).

Banjo error codes have their most significant byte set to 0x00. User applications can use non-zero values in the MSB to define their own error codes without conflicting with Banjo.

Parameters
codeThe error code to check.
Returns
Non-zero if the error is user-defined, zero if it's a Banjo error.

Definition at line 331 of file error.h.

◆ bj_error_code_kind

#define bj_error_code_kind ( code)
Value:
((code) & 0x000000FFu)

Extracts the kind (category) from an error code.

The kind is the least significant byte, shared by all errors in the same category. For example:

#define bj_error_code_kind(code)
Extracts the kind (category) from an error code.
Definition error.h:319
@ BJ_ERROR_CANNOT_ALLOCATE
Memory allocation failed.
Definition error.h:267
@ BJ_ERROR_SYSTEM
Generic operating system error.
Definition error.h:263
@ BJ_ERROR_INVALID_DATA
Generic invalid data error.
Definition error.h:283
@ BJ_ERROR_INVALID_FORMAT
Data format does not match expected format.
Definition error.h:285
Parameters
codeThe error code to extract the kind from.
Returns
The kind portion of the error code.

Definition at line 319 of file error.h.

Enumeration Type Documentation

◆ bj_error_code

A numeric representation of an error in Banjo.

Error codes are 32-bit values with a hierarchical structure:

+----------+------------------+------------+
| Origin | Specific Code | Kind |
| (8 bits) | (16 bits) | (8 bits) |
+----------+------------------+------------+
MSB LSB
  • Origin (MSB): 0x00 for Banjo errors, non-zero for user-defined.
  • Specific Code: Unique identifier within the kind.
  • Kind (LSB): Category shared by related errors.

For example, BJ_ERROR_INCORRECT_VALUE is 0x00000204:

  • Origin: 0x00 (Banjo)
  • Specific: 0x0002
  • Kind: 0x04 (same as BJ_ERROR_INVALID_DATA)

Use bj_error_code_kind to extract the kind from any error code. Use bj_error_code_is_user to check if an error is user-defined.

Enumerator
BJ_ERROR_NONE 

No error occurred.

BJ_ERROR 

General unspecified error.

BJ_ERROR_UNSUPPORTED 

Operation not supported on this platform or configuration.

BJ_ERROR_NOT_IMPLEMENTED 

Feature not yet implemented.

BJ_ERROR_SYSTEM 

Generic operating system error.

BJ_ERROR_FILE_NOT_FOUND 

Requested file was not found.

BJ_ERROR_CANNOT_ALLOCATE 

Memory allocation failed.

BJ_ERROR_INITIALIZE 

System component initialisation failed.

BJ_ERROR_DISPOSE 

System component cleanup failed.

BJ_ERROR_IO 

Generic I/O error.

BJ_ERROR_CANNOT_READ 

Error while reading from a file or stream.

BJ_ERROR_CANNOT_WRITE 

Error while writing to a file or stream.

BJ_ERROR_INVALID_DATA 

Generic invalid data error.

BJ_ERROR_INVALID_FORMAT 

Data format does not match expected format.

BJ_ERROR_INCORRECT_VALUE 

Value does not match expected value.

BJ_ERROR_VIDEO 

Error in video/graphics subsystem.

BJ_ERROR_AUDIO 

Error in audio subsystem.

BJ_ERROR_NETWORK 

Error in network subsystem.

BJ_ERROR_NETWORK_SOCKET 

Socket error.

BJ_ERROR_NETWORK_TIMEOUT 

Operation timed out.

Examples
handling_errors.c, net_tcp_nonblocking.c, and net_tcp_timeout.c.

Definition at line 249 of file error.h.

Function Documentation

◆ bj_clear_error()

void bj_clear_error ( struct bj_error ** error)

Frees an error and sets the pointer to NULL.

Safe to call with a NULL pointer or pointer to NULL.

bj_clear_error(&err); // err is now NULL
bj_clear_error(&err); // Safe, does nothing
void bj_clear_error(struct bj_error **error)
Frees an error and sets the pointer to NULL.
Parameters
errorPointer to error to free.
Examples
handling_errors.c, net_tcp_client.c, net_tcp_nonblocking.c, net_tcp_server.c, net_tcp_timeout.c, net_udp_broadcast_recv.c, net_udp_broadcast_send.c, net_udp_client.c, and net_udp_server.c.

Referenced by demonstrate_error_copy(), demonstrate_error_matching(), and main().

◆ bj_copy_error()

struct bj_error * bj_copy_error ( const struct bj_error * error)

Creates a copy of an error.

Returns a newly allocated error with the same code and message. The caller owns the copy and must free it with bj_clear_error.

Parameters
errorThe error to copy. May be NULL.
Returns
A new error copy, or NULL if error was NULL.
Examples
handling_errors.c.

Referenced by demonstrate_error_copy().

◆ bj_error_code()

uint32_t bj_error_code ( const struct bj_error * error)

Gets the error code from an error object.

Parameters
errorThe error to query. May be NULL.
Returns
The error code, or BJ_ERROR_NONE if error is NULL.

◆ bj_error_matches()

bj_bool bj_error_matches ( const struct bj_error * error,
uint32_t code )

Checks if an error matches a specific error code.

Returns true if error is non-NULL and has the specified code. Use this for exact matching of error codes.

// Handle missing file specifically
}
Parameters
errorThe error to check. May be NULL.
codeThe error code to match against.
Returns
BJ_TRUE if error matches the code, BJ_FALSE otherwise.
See also
bj_error_matches_kind For matching Error Management categories.
Examples
handling_errors.c.

Referenced by demonstrate_error_matching().

◆ bj_error_matches_kind()

bj_bool bj_error_matches_kind ( const struct bj_error * error,
uint32_t kind )

Checks if an error belongs to a specific error kind (category).

Returns true if error is non-NULL and its kind matches the specified kind. Use this to handle categories of errors uniformly.

// Handle any I/O error (read, write, etc.)
}
Parameters
errorThe error to check. May be NULL.
kindThe error kind to match (e.g., BJ_ERROR_IO, BJ_ERROR_SYSTEM).
Returns
BJ_TRUE if error's kind matches, BJ_FALSE otherwise.
Examples
handling_errors.c.

Referenced by demonstrate_error_matching().

◆ bj_error_message()

const char * bj_error_message ( const struct bj_error * error)

Gets the error message from an error object.

The returned string is owned by the error object and remains valid until the error is freed or modified.

Parameters
errorThe error to query. May be NULL.
Returns
The error message, or NULL if error is NULL.
Examples
handling_errors.c, net_tcp_client.c, net_tcp_nonblocking.c, net_tcp_server.c, net_tcp_timeout.c, net_udp_broadcast_recv.c, net_udp_broadcast_send.c, net_udp_client.c, and net_udp_server.c.

Referenced by demonstrate_error_copy(), demonstrate_error_matching(), and main().

◆ bj_prefix_error()

void bj_prefix_error ( struct bj_error ** error,
const char * prefix )

Adds a prefix to an existing error's message.

Prepends text to the error message to add context about where/why the error occurred.

if (err != NULL) {
bj_prefix_error(&err, "In function foo: ");
// Message is now "In function foo: original message"
}
void bj_prefix_error(struct bj_error **error, const char *prefix)
Adds a prefix to an existing error's message.

If *error is NULL, this function does nothing.

Parameters
errorPointer to the error to modify.
prefixNull-terminated prefix string.
See also
bj_prefix_error_fmt For formatted prefixes.

◆ bj_prefix_error_fmt()

void bj_prefix_error_fmt ( struct bj_error ** error,
const char * format,
... )

Adds a formatted prefix to an existing error's message.

Like bj_prefix_error, but with printf-style formatting.

bj_prefix_error_fmt(&err, "While processing item %d: ", index);
void bj_prefix_error_fmt(struct bj_error **error, const char *format,...)
Adds a formatted prefix to an existing error's message.
Parameters
errorPointer to the error to modify.
formatPrintf-style format string for the prefix.
...Format arguments.
Examples
handling_errors.c.

Referenced by initialize_server().

◆ bj_propagate_error()

void bj_propagate_error ( struct bj_error ** dest,
struct bj_error * src )

Propagates an error to the caller's error location.

Transfers ownership of src to *dest. After this call, src is consumed and must not be used or freed.

If dest is NULL, the error is logged and freed (caller doesn't want it). If *dest is already non-NULL, src is logged and freed (first error wins).

Typical usage:

bj_error* local_err = NULL;
do_something(&local_err);
if (local_err != NULL) {
bj_propagate_error(error, local_err);
return FAILURE;
}
Parameters
destCaller's error location. May be NULL.
srcError to propagate. Consumed by this call.
See also
bj_propagate_prefixed_error To add context while propagating.
Examples
handling_errors.c.

Referenced by initialize_server().

◆ bj_propagate_prefixed_error()

void bj_propagate_prefixed_error ( struct bj_error ** dest,
struct bj_error * src,
const char * format,
... )

Propagates an error with an added prefix.

Combines bj_prefix_error_fmt and bj_propagate_error in one call. Adds context to the error message before propagating.

bj_propagate_prefixed_error(error, local_err,
"While loading '%s': ", filename);
Parameters
destCaller's error location. May be NULL.
srcError to propagate. Consumed by this call.
formatPrintf-style prefix format.
...Format arguments.
Examples
handling_errors.c.

Referenced by initialize_server().

◆ bj_set_error()

void bj_set_error ( struct bj_error ** error,
uint32_t code,
const char * message )

Creates a new error with a literal message.

Allocates and initialises a new error object. Use this when the error message is a compile-time constant or doesn't need formatting.

If *error is already non-NULL, the new error is logged but not stored, preserving the original error (first error wins).

If error is NULL, the error is logged in debug builds but no allocation occurs (zero cost).

Parameters
errorPointer to error location. May be NULL.
codeError code from bj_error_code or user-defined.
messageNull-terminated error message.
See also
bj_set_error_fmt For formatted messages.
bj_clear_error To free the Error Management.
Examples
handling_errors.c.

Referenced by load_config_file().

◆ bj_set_error_fmt()

void bj_set_error_fmt ( struct bj_error ** error,
uint32_t code,
const char * format,
... )

Creates a new error with a formatted message.

Like bj_set_error, but accepts printf-style format arguments.

"Cannot open '%s': %s", path, strerror(errno));
Parameters
errorPointer to error location. May be NULL.
codeError code.
formatPrintf-style format string.
...Format arguments.
See also
bj_set_error For literal messages (slightly more efficient).
Examples
handling_errors.c.

Referenced by open_network_port().