2025-06-21

error handling semantics

this section defines a structured model for categorizing error-handling strategies using a multi-dimensional tuple system. each strategy is described by the interaction of key properties such as signal form, error content, control flow, and propagation scope.

coordinate model

each error-handling pattern is described as a 5-tuple: (signal, content, control, propagation, binding)

variables

  • signal: how the presence of an error is indicated

    • status-value: success/failure flag (e.g. bool, errno, 0/-1)
    • designated-value: error encoded in otherwise valid return type (e.g. null, -1, 0)
    • multiple-values: error signal separated from result via tuple or out parameter
    • exception: non-local jump with implicit error indication
    • continuation: alternate execution path taken by subroutine selection
    • global-flag: external variable set on error
  • content: what information is attached to the error

    • none: no detail (e.g. false, 0)
    • code: numeric identifier or enum
    • message: human-readable string
    • structured: compound object with code, group, trace, etc.
  • control: what control mechanism is used to handle errors

    • inline-check: condition after each call
    • local-jump: jump to local error handler or cleanup
    • non-local-jump: unwind to non-local handler (e.g. exceptions)
    • continuation: alternate subroutine for error
    • passive: result is passed on unchecked
  • propagation: how errors move through the call chain

    • explicit: caller checks and passes errors manually
    • implicit: errors bubble automatically (e.g. exceptions)
    • intercepted: errors handled internally and suppressed
    • reified: error values are passed as objects to be manipulated
  • binding: how error-handling logic is connected to the call

    • manual: user-written checks after each call
    • protocol: macros or helpers standardize checking
    • handler: block or function bound to handle failure (e.g. try/catch)
    • global: global state inspected or reset

common patterns

  • process exit code

    • (status-value, code, inline-check, explicit, global)
    • simple success/failure convention, commonly used in shell environments
  • designated return value

    • (designated-value, none, inline-check, explicit, manual)
    • special values like null or -1 indicate failure, with optional follow-up check
  • status + output values

    • (multiple-values, code, inline-check, explicit, manual)
    • primary output separated from status, both must be unpacked and checked
  • error object return

    • (multiple-values, structured, inline-check, reified, manual)
    • richer errors represented as structs or objects, manually passed along
  • structured error with macros

    • (status-value, structured, local-jump, explicit, protocol)
    • helpers abstract checking and redirection to an error label
  • exception handling

    • (exception, structured, non-local-jump, implicit, handler)
    • errors trigger a jump up the call stack and are caught at a handler block
  • continuation passing

    • (continuation, structured, continuation, implicit, handler)
    • separate success and error paths passed explicitly as functions
  • global error flag

    • (global-flag, code, passive, implicit, global)
    • error state read via global or thread-local variable

excluded patterns

  • static-only error signals with no runtime flexibility
  • stateful patterns that do not compose (e.g. non-thread-local globals)
  • multi-layered error propagation where binding cannot be tracked here is the integrated expansion section revised and formatted to match the structured systematization document. it fits as a continuation beneath the systematized model, providing elaboration, rationale, tradeoffs, and examples in human-readable form.

practical considerations and examples

before implementation begins, a developer should explicitly select an error-handling strategy. failing to do so often leads to code in which failures are inconsistently handled, hard to trace, difficult to recover from, or not handled at all. ad-hoc mechanisms that have been woven-in into large codebases resist harmonization.

definition of error

subroutines typically have a primary goal-e.g. dividing two numbers or writing to a file. this goal can fail due to:

  • unavailable external resources (e.g. file system access, web service failure)
  • invalid input arguments (e.g. division by zero, unhandled cases) these conditions are considered errors. it is useful to distinguish between operations that are guaranteed not to fail (e.g. integer addition) and those that may fail (e.g. memory allocation, file i/o).

designated types or values as errors

some systems use specific types or values to indicate failure:

  • false, null, or empty string
  • negative values or magic constants (e.g. -1, eof)
  • strings containing error descriptions pros:
  • easy to implement and recognize
  • compatible with simple return-type models cons:
  • ambiguity: some values may be valid and also used to signal errors
  • the meaning must be known in advance

default values on failure

some subroutines return a default or neutral value (e.g. 0, empty array) in place of a real result when failing. pros:

  • simple to check (if length == 0)
  • allows continued processing in some cases cons:
  • ambiguity: was 0 a result or a signal of failure?
  • not appropriate if the default is also a valid result

multiple return values and output arguments

error status may be returned separately from the actual result. for example:

input, output, -> status

instead of:

input, -> (output, status)

notes:

  • requires agreement on result and status position
  • often treated like returning a compound result
  • can complicate composition by forcing explicit error checks

continuation-passing styles

two common forms:

  • with error argument: result and error passed to a callback
  • with subroutine selection: separate handlers passed for success and failure pros:
  • aligns well with asynchronous programming (e.g. node.js)
  • errors are handled as part of control flow cons:
  • inconsistent conventions between libraries
  • nested subroutines increase visual and structural complexity

local jump-based handling

errors are detected and control jumps to a local cleanup or handler section within the same function. pros:

  • localized and predictable flow
  • reduces duplication of cleanup code
  • avoids unwinding the call stack

non-local jumps and exceptions

errors exit the current call context and propagate up the stack until a handler is reached. pros:

  • allows centralized error handling across call layers
  • preserves call composition without interleaving status checks cons:
  • introduces hidden control flow
  • cleanup must still be ensured (e.g. freeing memory)
  • error must eventually be handled to resume or exit safely

global error state

some systems set a global or thread-local error variable (e.g. errno). cons:

  • needs explicit resets to avoid stale errors
  • not safe across threads without extra care
  • error data is external to the call return path

propagation and reporting challenges

  • using integers for error codes across libraries may lead to overlap
  • supplemental metadata like string tags as source identifiers may be needed
  • errors can be reported to logs or stderr even if not programmatically handled

structured error content

typical fields for detailed error representation include:

error-name
error-description
source-routine-name
source-line-number
source-file
source-module-name
backtrace
literal-irritant-expression

examples

process exit codes

  • 0 means success; any other code indicates failure
mkdir new_directory && cd new_directory

libc model

  • functions return -1, null, or special constants on error
  • errno holds the associated code

structured errors in c

sph-sc-lib uses a status struct with an integer id and group string, along with helper macros to propagate and branch on error:

int main() {
  status_declare;
  // code ...
  status_require(test());
  // more code ...
exit:
  return status.id;
}

repository