Error Code Overview
Purpose
The c_error.h interface defines a common error model for the csalt library.
This error handling model provides a protable alternative to errno which is
not portable, nor defined in any standard.
Approach
The c_error.h module provides a flexible error-handling framework that can
be used independently by consumers of the csalt library. Within csalt,
however, a hierarchical error-handling strategy is applied. This strategy
recognizes that not all API surfaces require the same level of diagnostic detail,
and that error reporting must be balanced against performance and usability.
Error reporting in csalt functions typically falls into one of the
following four categories:
No Error Reporting The function provides no indication of success or failure. Such functions are typically internal utilities or performance-critical primitives where input validity is assumed and misuse constitutes a coding error rather than a runtime condition.
Potential Error (Ambiguous Return Value) The function returns a value that may indicate an error, but the return value itself could also represent a valid condition. Examples include: - returning
NULLfrom an allocation function, - returning0when0may be a valid size or count.In these cases, callers must interpret results based on context.
Error Indication (Unambiguous Failure) The function returns a value that always indicates failure (e.g.,
NULL), but provides no information about the cause. Users must consult the documentation to understand possible failure modes.Error Description (Preferred Maximum Detail) The function returns an
expected-style struct such asarena_expect_torvoid_ptr_expect_tthat: - explicitly indicates success or failure, and - provides a specificerror_code_tdescribing the failure cause.This form provides the most complete diagnostic information.
Design Rationale
Although type-4 error handling is the most expressive, it also introduces
overhead: callers must unpack a result structure and branch on success or
failure, which may be undesirable in extremely hot code paths. Therefore,
csalt applies the following guiding principles:
Hot-path functions (e.g.,
alloc_arena) typically use type 2 or 3 error handling to avoid overhead in tight loops.Initialization functions, resource acquisition, and configuration APIs typically use type 4 error handling because failures are rare but important to diagnose accurately.
Coding errors (e.g., passing
NULLwhere a valid pointer is required) are generally not the target of the error-handling system. These are best identified by: - debug builds, - assertions, - static analysis, or - MISRA compliance tools.If type-4 handling is in use for a given function, coding errors may still manifest as
error_code_tresults, but this is considered a byproduct rather than an intentional feature.
Summary
The error-handling model in csalt is intentionally flexible. It allows users
to select simple or detailed error reporting based on their needs while the
library maintains clear conventions that balance safety, diagnosability, and
performance.
Design Notes
Status location: Each csalt object carries its own
error_code_tso status is scoped to the object that was acted upon (no global state).Return values: Functions may return useful values directly (e.g., sizes, counts) while reporting failure through the object’s error field.
String conversion: Use
error_to_string()to produce a human-readable description suitable for any logging framework (the csalt core remains logger-agnostic).
API Reference
ErrorCode enumeration (overview)
The table below summarizes each error_code_t value, its category, and intended use.
Authoritative comments live in c_error.h; this overview is for quick reference.
Name |
Value |
Category |
Description |
|---|---|---|---|
|
|
meta |
Sentinel indicating an invalid error argument was provided to an API expecting a valid |
|
|
none |
Operation completed successfully. |
|
|
argument |
Invalid function argument or combination. |
|
|
argument |
A required pointer argument was |
|
|
argument |
Index was outside valid range for the target container/object. |
|
|
argument |
Dimensions/shapes of inputs are incompatible for the requested operation. |
|
|
argument |
Attempt to access or operate on an uninitialized element/field. |
|
|
argument |
Dangling/invalid iterator or cursor. |
|
|
argument |
Required precondition not satisfied. |
|
|
argument |
Postcondition or invariant failed. |
|
|
argument |
API call not valid for the current state. |
|
|
memory |
Memory allocation failed (e.g., |
|
|
memory |
Reallocation failed ( |
|
|
memory |
System out of memory or allocator could not satisfy the request. |
|
|
memory |
Size/length arithmetic overflow (e.g., |
|
|
memory |
Capacity limit exceeded (policy or representable limit). |
|
|
memory |
Required alignment not satisfied. |
|
|
state |
Internal invariant violated / corruption detected. |
|
|
state |
Double-initialization or conflicting initialization. |
|
|
state |
Requested item/key not found. |
|
|
state |
Container empty when an element was required. |
|
|
state |
Container modified during iteration (fail-fast). |
|
|
state |
Value below range. |
|
|
state |
Value above range. |
|
|
math |
Division by zero detected during computation. |
|
|
math |
Matrix is singular / non-invertible for the requested operation. |
|
|
math |
Numeric overflow/underflow (range error). |
|
|
math |
Math domain error (input outside valid domain). |
|
|
math |
Excessive loss of numeric precision / instability. |
|
|
I/O |
File/handle could not be opened (missing, permissions, etc.). |
|
|
I/O |
Error while reading from a file/stream. |
|
|
I/O |
Error while writing to a file/stream. |
|
|
I/O |
Permission/ACL denied. |
|
|
I/O |
Operation interrupted (e.g., |
|
|
I/O |
I/O timed out. |
|
|
I/O |
Operation on a closed stream/descriptor. |
|
|
I/O |
Non-blocking operation would block. |
|
|
type/format |
Incompatible or unexpected type encountered. |
|
|
type/format |
Invalid/unsupported data format. |
|
|
type/format |
Invalid text encoding (e.g., UTF-8). |
|
|
type/format |
Parsing error. |
|
|
type/format |
Data failed validation rules. |
|
|
concurrency |
Failed to acquire/initialize lock. |
|
|
concurrency |
Potential or actual deadlock detected. |
|
|
concurrency |
Thread create/join failure. |
|
|
concurrency |
Operation cancelled. |
|
|
concurrency |
Data race / unsynchronized access. |
|
|
config |
Invalid configuration value/state. |
|
|
config |
Unsupported feature or platform. |
|
|
config |
Feature disabled by policy/build flags. |
|
|
config |
ABI/format/library version mismatch. |
|
|
config |
Non-memory resource exhausted (e.g., file descriptors). |
|
|
generic |
Functionality not yet implemented. |
|
|
generic |
Operation unavailable in current build/runtime. |
|
|
generic |
Fallback for unspecified/unknown failures. |
Legend
argument (-1xx): invalid inputs, NULLs, bounds, uninitialized access, pre/postcondition failures, illegal state for the call
memory (-2xx): allocation/reallocation failures, out-of-memory, size/capacity arithmetic overflow, alignment issues
state (-3xx): container/engine state problems (corruption, already initialized, not found, empty, concurrent modification)
math (-4xx): arithmetic/domain issues (divide by zero, singular matrices, range/overflow, excessive precision loss)
I/O (-5xx): filesystem/stream/socket operations (open/read/write, permissions, interrupted, timeout, closed, would-block)
type/format (-6xx): type mismatches, invalid format/encoding, parsing and validation failures
concurrency (-7xx): locking/threading issues (lock failure, deadlock, cancellation, data race)
config (-8xx): configuration/policy/environment (unsupported features, disabled by policy, version mismatch, non-memory resource exhaustion)
generic (-9xx): not implemented / unavailable / unknown
meta: sentinel values not produced by normal operations (e.g.,
INVALID_ERROR = 1); none: success (NO_ERROR = 0)
See also
error_to_string()— convert anyerror_code_tto a short human-readable message (strings defined in code comments).
Logging Helper Functions
The following function converts error codes into textual descriptions:
error_to_string
-
const char *error_to_string(error_code_t code)
Convert an error_code_t to a short human-readable message.
Returns a stable, static string literal describing
code. Messages are aligned with the range-based taxonomy (−1xx argument, −2xx memory, …) and are suitable for logs, error prompts, or test assertions.Error reporting:
Pure function: does not modify global state (e.g.,
errno).
- Example
error_code_t ec = BAD_ALLOC; fprintf(stderr, "[%s/%d] %s\n", error_category_tag(ec), (int)ec, error_to_string(ec)); // -> "[MEM/-201] Memory allocation failed"
Note
The mapping is intentionally concise and stable for logging. For a broader label (category), see error_cat_to_string or ::error_category_str. To translate between error_code_t and
errno, use set_errno_from_error and error_from_errno.- Parameters:
code – The error_code_t to describe.
- Return values:
Unrecognized error code – Returned when
codeis outside the known enumerators (fallback path).- Returns:
Pointer to a constant, NUL-terminated string. The pointer refers to a static string literal that must not be freed or modified.
error_cat_to_string
-
const char *error_cat_to_string(ErrorCategory cat)
Convert an ErrorCategory to an uppercase label for logging.
Maps the range-based category (e.g., ECAT_MEM) to a stable, uppercase description such as “MEMORY ERROR”. Useful for log prefixes and summaries. Returns a pointer to a static string literal; do not free or modify it.
- Example
ErrorCategory cat = ECAT_MEM; fprintf(stderr, "[%s] allocation failed\n", error_cat_to_string(cat)); // -> "[MEMORY ERROR] allocation failed"
Note
Pure function: does not modify global state (e.g.,
errno) and is thread-safe. If you prefer shorter tags (e.g., “MEM”, “IO”), provide a companion function such aserror_category_tag().- Parameters:
cat – The ErrorCategory value (e.g., ECAT_ARG, ECAT_IO).
- Returns:
A constant NUL-terminated string: one of “NO ERROR”, “ARGUMENT ERROR”, “MEMORY ERROR”, “STATE ERROR”, “MATH ERROR”, “I/O ERROR”, “TYPE/FORMAT ERROR”, “CONCURRENCY ERROR”, “CONFIG ERROR”, “GENERIC ERROR”, or “UNKNOWN ERROR” for out-of-range values.
Error Category Predicates
These helpers classify an error_code_t into the range-based taxonomy
(-1xx argument, -2xx memory, …). Each function returns true iff the code
belongs to that category.
#include "c_error.h"
void log_by_category(error_code_t ec) {
/* Fast boolean checks (header-defined static inline) */
if (ec_is_mem(ec)) {
fprintf(stderr, "[MEM/-2xx] %s\n", error_to_string(ec));
} else if (ec_is_io(ec)) {
fprintf(stderr, "[IO/-5xx] %s\n", error_to_string(ec));
} else if (ec_is_arg(ec)) {
fprintf(stderr, "[ARG/-1xx] %s\n", error_to_string(ec));
} else if (ec_is_state(ec)) {
fprintf(stderr, "[STATE/-3xx] %s\n", error_to_string(ec));
} else if (ec_is_math(ec)) {
fprintf(stderr, "[MATH/-4xx] %s\n", error_to_string(ec));
} else if (ec_is_fmt(ec)) {
fprintf(stderr, "[FMT/-6xx] %s\n", error_to_string(ec));
} else if (ec_is_conc(ec)) {
fprintf(stderr, "[CONC/-7xx] %s\n", error_to_string(ec));
} else if (ec_is_cfg(ec)) {
fprintf(stderr, "[CFG/-8xx] %s\n", error_to_string(ec));
} else if (ec_is_gen(ec)) {
fprintf(stderr, "[GEN/-9xx] %s\n", error_to_string(ec));
} else {
/* NO_ERROR / INVALID_ERROR (meta) or out-of-range */
fprintf(stderr, "[NONE] %s\n", error_to_string(ec));
}
}
-
static inline bool ec_is_arg(error_code_t ec)
True if
ecis an argument/input error (-1xx).
-
static inline bool ec_is_mem(error_code_t ec)
True if
ecis a memory/allocation error (-2xx).
-
static inline bool ec_is_state(error_code_t ec)
True if
ecis a state/container error (-3xx).
-
static inline bool ec_is_math(error_code_t ec)
True if
ecis a math/domain error (-4xx).
-
static inline bool ec_is_io(error_code_t ec)
True if
ecis an I/O error (-5xx).
-
static inline bool ec_is_fmt(error_code_t ec)
True if
ecis a type/format error (-6xx).
-
static inline bool ec_is_conc(error_code_t ec)
True if
ecis a concurrency error (-7xx).
-
static inline bool ec_is_cfg(error_code_t ec)
True if
ecis a config/policy error (-8xx).
-
static inline bool ec_is_gen(error_code_t ec)
True if
ecis a generic/fallback error (-9xx).
Error Handling Marco
The library provides a small set of convenience macros to model C++23-style
std::expected<T, error_code_t> in C. These macros are documented in detail
in the C headers; this section simply exposes those docstrings into the
Sphinx documentation. If the user wishes to ommit macros for explicit type
performance, especially for scenarios where the code must meet MISRA standards
these macros can be omitted by compiling with the STATIC_ONLY flag.
Each macro below is pulled directly from its Doxygen doc comment, so the authoritative description always lives alongside the implementation. Provide breif intro here.
Data Structure
The error handling struct used in this library implements the following syntax. In the example it is implemented as a function like macro, but a user can implement their own explicit struct so as long as it adheres to this construct.
#define DEFINE_EXPECTED_TYPE(name, T) \
typedef struct name { \
bool has_value; \
union { \
T value; \
ErrorCode error; \
} u; \
} name
Defied Expected Structures
The following versions of the DEFINE_EXPECTED_TUPE macros are pre defined
in the c_error.h file.
uint8_t uint8_expect_t
int8_t int8_expect_t
uint32_t uint32_expect_t
int32_t int32_expect_t
uint64_t uint64_expect_t
int64_t int64_expect_t
float float_expect_t
double double_expect_t
long double ldouble_expect_t
void* void_ptr_expect_t
uint8_t* uint8_ptr_expect_t
int8_t* int8_ptr_expect_t
uint16_t* uint16_ptr_expect_t
int16_t* int16_ptr_expect_t
uint32_t* uint32_ptr_expect_t
int32_t* int32_ptr_expect_t
uint64_t* uint64_ptr_expect_t
int64_t* int64_ptr_expect_t
float* float_ptr_expect_t
double* double_ptr_expect_t
long double* ldouble_ptr_expect_t
DEFINE_EXPECTED_TYPE
-
DEFINE_EXPECTED_TYPE(name, T)
Defines a tagged-union “expected<T>” type similar to C++23
std::expected.This macro declares a struct named
namethat represents either:a successfully produced value of type
T, oran error of type error_code_t.
Internally, the struct contains:
bool has_value— indicates whether the union currently stores a valueunion { T value; error_code_t error; } u— holds either the success value or error
The tag
has_valuemust be checked before accessingu.valueoru.error.// Define an expected type for integers DEFINE_EXPECTED_TYPE(expected_int_t, int); // A function that returns expected_int_t expected_int_t safe_divide(int a, int b) { if (b == 0) { return EXPECTED_ERR(expected_int_t, DIV_BY_ZERO); } return EXPECTED_OK(expected_int_t, a / b); } // Using the expected type expected_int_t r = safe_divide(10, 2); if (!r.has_value) { printf("Error: %d\n", r.u.error); } else { printf("Result: %d\n", r.u.value); }
EXPECTED_OK
-
EXPECTED_OK(TypeName, vexpr)
Constructs a success result for an expected-type value.
Creates a temporary instance of the expected type
TypeNamewith:has_value = trueu.value = vexpr
This macro evaluates
vexprexactly once and is safe for side-effects.DEFINE_EXPECTED_TYPE(expected_float_t, float); expected_float_t sqrt_expected(float x) { if (x < 0.0f) { return EXPECTED_ERR(expected_float_t, DOMAIN_ERROR); } return EXPECTED_OK(expected_float_t, sqrtf(x)); }
EXPECTED_ERR
-
EXPECTED_ERR(TypeName, eexpr)
Constructs an error result for an expected-type value.
Creates a temporary instance of the expected type
TypeNamewith:has_value = falseu.error = eexpr
This macro evaluates
eexprexactly once and sets the stored error code.DEFINE_EXPECTED_TYPE(expected_int_t, int); expected_int_t parse_int(const char *s) { if (!s) { return EXPECTED_ERR(expected_int_t, NULL_POINTER); } char *end = NULL; long val = strtol(s, &end, 10); if (end == s || *end != '\0') { return EXPECTED_ERR(expected_int_t, PARSING_FAILED); } return EXPECTED_OK(expected_int_t, (int)val); }
EXPECTED_HAS_VALUE
-
EXPECTED_HAS_VALUE(r)
Checks whether an expected-type result holds a value or an error.
This macro evaluates the tag field of an expected-type instance
r. It returnstrueif the result contains a valid success value, orfalseif it contains an error_code_t.This is the C equivalent of C++23
std::expected::has_value()or the implicitoperator bool()check.DEFINE_EXPECTED_TYPE(expected_int_t, int); expected_int_t r = safe_divide(10, 0); if (!EXPECTED_HAS_VALUE(r)) { printf("Error code: %d\n", EXPECTED_ERROR(r)); } else { printf("Quotient: %d\n", EXPECTED_VALUE(r)); }
EXPECTED_VALUE
-
EXPECTED_VALUE(r)
Retrieves the value stored in an expected-type result.
This macro extracts the success value from
r. Only call this macro if is true.Accessing the value when an error is present results in undefined behavior, as the union member
valueis not active.expected_int_t r = safe_divide(100, 4); if (EXPECTED_HAS_VALUE(r)) { int v = EXPECTED_VALUE(r); // OK printf("Value = %d\n", v); } else { // Handle error }
EXPECTED_ERROR
-
EXPECTED_ERROR(r)
Retrieves the error code stored in an expected-type result.
This macro extracts the error_code_t from
r. Only call this macro if is false.Reading
u.errorwhen the success-value branch is active results in undefined behavior.expected_int_t r = safe_divide(10, 0); if (!EXPECTED_HAS_VALUE(r)) { error_code_t ec = EXPECTED_ERROR(r); fprintf(stderr, "Operation failed: %d\n", ec); }