andersch.dev

<2024-01-08 Mon>

Error Handling

In programming, error handling refers to how a program responds to the occurrence of errors (or exceptions) during the execution of a program.

The two most common mechanisms for error handling are return codes (return values of a function indicate success/failure) and exceptions (propagate errors up the call stack and try-catch them).

Principles

Nil Struct Pointers

  • Error code returning nullpointers should instead return nil struct pointers
  • This makes any pointer returned readable - even if the data is invalid
  • Receiving code can work with the values of the nil struct in the same codepath
  • Zero should be adopted as a useful default value (ZII)

Zero-Is-Initialization (ZII)

  • ZII means having zero-ed out memory be the default initialization
  • Any codepath handling zeroed out types should still work
  • E.g. a zero-initialized string view will be interpreted as an empty string
  • Nil struct pointers are an exception to the ZII rule.
  • There is a trade-off to consider between ZII and using nil structs
  • E.g., reading the first value vs. terminating loops at 0 when batch processing
  • However, both ZII and nil struct pointers can work together
  • E.g., make zero indices/handles map to a nil struct pointer

If You're Going To Fail, Fail Early (Preallocate)

  • Receiving a nil struct pointer and writing to it should not be possible
  • An allocation (e.g. with malloc) may succeed or not depending on many factors
  • For the user of an API, if the allocation fails, the earlier the better
  • Rule-of-thumb: Make allocation failures occur in shallow callstack frames
  • Arena allocators already abide by this rule
  • Pointer are guaranteed to be valid

Prefer Fewer Types (AND over OR)

  • More types means more required codepaths.
  • Since sum types (OR) encode valid &
  • invalid results, they cause an increase in codepaths.
  • Instead, prefer product types (AND)
  • return error information in addition to whatever the "non-error result" is.
  • One codepath processes valid results (or no-ops for ZII data) and error info

Error Information Side-Channels

  • errno (global, thread-local int) set as a side-effect of functions erroring
  • Thus: error info available in addition to (rather than instead of) valid data
  • Usage code can check when needed
  • Problem: Can inform usage code of the most recent error
  • Problem: No way of knowing which code set errno last, and when.
  • However, this is all doable with error information "side-channels"
  • Each thread has an arena: per_thread Arena *log_arena
  • Each thread has a msg log: per_thread MsgList log_msgs
  • Messages stored in the MsgList can have a callstack, a string, color, etc.
  • Error information is accumulated in a log to be later inspected

Resources