error handling

about dealing with unexpected, or unusual, return states of subroutines

usually subroutines have a main purpose, for example dividing two numbers or writing to a file, but sometimes this cant be fulfilled because for example:

  • external resources are unavailable, like inaccessible filesystem paths or error responses from webservices
  • the arguments are not supported: invalid data types, division by zero, missing data

states likes this are then often considered to be errors

it can be useful to think of operations that cant fail (for example addition; as long as hardware and compiler works) and operations that can fail (for example memory access)

how to communicate errors

types designated for errors

for example

  • boolean false
  • strings that are always considered error descriptions
  • null value types

values designated as errors

for example

  • negative or positive number on error
  • true on success boolean false on error


  • type checks are usually fast and subroutines often return a limited number of different types


  • problematic when all types can be valid results

error type

for example

  • a distinct type with fields for error identification, error group, description and possibly more

default values

for example

  • zero on failed parsing of a string to number


  • potentially ambiguous results (parsed zero string or failed parsing?)


  • result is usually a valid return type to proceed and might be less likely to disturb following computation

multiple return values

for example

  • return value as error status and additional output arguments for other values
  • additional error describing values returned when the language supports multiple return values

the additional values still have to be unpacked syntactically, which needs additional forms to create and accept multiple return values. the position of the error value in the collection of output values has to be agreed on between different libraries for consistency. similar to returning a single compound value or special error type. for every subroutine call and return, code has to be added to check for an error status and it becomes a kind of manual flow control, with code that makes a call and checks if it shall continue. this interrupts subroutine composition

with output arguments

the error status can be passed as the subroutine result or output argument (or additional value). compare the following two signatures

input-value ... output-value ... output-status -> value
input-value ... output-value ... -> status

the latter should be simpler because there is always a status value and only one, and it isnt mixed with other output-values

local jumps

not exiting the subroutine but continuing at a place at the end of the subroutine where errors are handled. cleanup, like deallocation before exit, can be done at this place. a local variable can be used to save error details where the error occurs. local jumps can also exit loops


  • fast
  • easy to predict execution flow
  • less duplication compared to handling errors at each source as all errors can be handled at one place

non-local jumps

error information can be collected and subroutines exited in the call stack upwards using non-local jumps until a catcher is met that handles the error. caller/callee return semantics are disregarded. this corresponds to exception handling using throw and catch. with exception handling, typically a backtrace is collected when an error occurs and a default catcher that exits the program is installed. exception handling can also be implemented with delimited continuations

cleanup (heap-memory, file handles or similar) might still be necessary on every non-local exit point. garbage collection helps with the memory management at least


  • errors that might originate from multiple different subroutines can be handled at once
  • routine composition is not interrupted by status checks and can therefore be simpler in notation


  • at some place, any errors might still need to be handled anyway
  • normally subroutines exit at the point of their call or the whole process is ended. hidden control flow paths might emerge
  • dont help if something needs to happen in context at every error source

continuation selection

multiple subroutine references passed to the subroutine and one is called on success the other on error. at some point the original subroutine can still return


  • signature of error handling routine has to be agreed on between different libraries for consistency
  • subroutine nesting, especially with anonymous functions

global variable

a global variable that is set to specific values on error. the variable needs to be thread local for mult-threaded error handling. the value persists between routine calls needs to be reset. might be harder to optimise by the compiler since the value can be changed from many places

other considerations

if multiple libraries use integer values for error ranges then error values of different libraries might overlap and be ambiguous. for this case, it can become necessary to keep additional information with error objects, for example a string identifier for the library it belongs to


error reporting is collecting details that may be helpful to find the cause and making it available to the user. for example a message written to standard output or a log file

error details

information that can be useful when handling an error:




most library functions return a special value to indicate that they have failed. the special value is typically -1, a null pointer, or a constant such as eof that is defined for that purpose. but this return value tells you only that an error has occurred. to find out what kind of error it was, you need to look at the error code stored in the variable errno

sph-sc-lib status

uses a struct for error integer identifier and string group. has helpers, that subroutine results can be passed to, that check the return value and go to the error label on error status. sph-sc-lib

int main() {
  // code ...
  // more code ...
  return status.id;