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