Errno-like C global error handling I'm thinking about a unified error handling interface. 

I've come to conclusion that the errno global variable style isn't so bad, and decided to implement an improved version of it: every function called sets an error code to a global error variable through the get/set functions.

The difference with the errno pattern is that the code is set even if there's no actual error (ERR_OK enum value), so we get a status of an operation after each function call. This allows us to not reserve a special "error" return value. The global error variable is of thread local storage type.

What pitfalls could be met with such approach?

err.h:

#ifndef ERR_H
#define ERR_H

enum err {
    ERR_OK
    ERR_SOME,
};

void err_set(enum err e);
int err_get(void);

#define ERR (err_get() != ERR_OK)

#endif /* ERR_H */


err.c:

#include "err.h"

_Thread_local static int error;

void err_set(enum err e) {
    error = e;
}

int err_get(void) {
    return error;
}


main.c:

#include &lt;stdio.h&gt;
#include "err.h"

static size_t get_size(void *arg) {
    if (!arg) {
        err_set(ERR_SOME);
        return 0;
    }

    err_set(ERR_OK);
    return sizeof(*arg);
}

int main(void) {
    void *bad = NULL;

    size_t size = get_size(bad);
    if (ERR) {
        printf("Bad pointer!\n");
        return 1;
    }

    printf("size == %zu\n", size);

    return 0;
}
Error handling is indeed an issue in C with a multitude of potential handling mechanisms.  Good to see your take on it.


Allow for atomic error handling.  In addition to getting the error, it would be useful to have a test and clear to allow simpler error testing.  The point is that once upper level code tests positive for an error (the error is caught), the global test status should be immediately cleared (atomic) to allow for subsequent setting without a chance of intervening code messing up things.

bool err_test_and_clear(int *err) {
  // Simplistic code.  See also "C11 7.17 Atomics &lt;stdatomic.h&gt;" 
  if (err) {
    *err = error;
  }
  bool e = error != ERR_OK;
  error = ERR_OK;
  return e;
}

// usage
foo();
int err;
if (err_test_and_clear(&amp;err)) { 
  foo_clean_up();  // code may cause an error
  fprintf(stderr, "Error: %d\n", err);  // report original error
}


vs.

// usage
foo();
if (ERR) {
  foo_clean_up(); // code may cause error - oops this should have been after below 2
  int err = err_get();
  err_set(ERR_OK);
  fprintf(stderr, "Error: %d\n", err);  // report maybe original error
}

Not a fan of ERR, which looks like a constant or object and it is not.  Perhaps make it look like a function. Or be emphatic that ERR_OK is 0.

// #define ERR (err_get() != ERR_OK)

// better
#define ERR() (err_get() != ERR_OK)

// or best no define, just use `if(err_get())`

If code like if (err_get() != ERR_OK) is preferred over if (err_get()), then code should use 

// _Thread_local static int error;
_Thread_local static int error = ERR_OK;

Missing ,

enum err {
    ERR_OK    , // add ,
    ERR_SOME,
};

I'm leaning that _Thread_local static int error may benefit with being volatile.  Have to ponder that more.
For easier to read semantics, consider a clear function.  err_clear(); has more clarity than err_set(ERR_OK); ("setting the error" of "OK error").  Further ERR_OK looks contradictory.  Perhaps ERR_NONE?

void err_clear(void) {
  error = ERR_OK;
}

Mixed "types".  I'd expect both int or both enum err

             vvvvvvvv
void err_set(enum err e);
int err_get(void);
^^^