HiveBrain v1.2.0
Get Started
← Back to all entries
debugcMinor

Rust-like "Result" in C - nicer error handling

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
resulthandlingerrorlikerustnicer

Problem

There are frequently things I miss when using C; one of them is a nice error-handling (exception-like) system. After trying Rust, I realized I could implement something similar in C itself. So here's my attempt at writing a Result type in C.

The idea of the Result type is similar to that of an Optional type: if the value was able to be computed, then we return that. Otherwise, if there was an error, we return some error. In my case, my errors are string messages.

I plan on using this for a class where we are required to use C, not C++ or other systems languages. We will be using gcc, so gcc specific behaviour is perfectly okay.

panic.h

#include 
#include 
#include 

/**
 * @brief panics with the error message, terminating the program
 * @details This function never returns. It writes `str` to `stderr` and exits
 *          with an error code of `1`.
 * 
 * @param str The error message displayed on termination
 */
void panic(const char * str) __attribute__((noreturn));

void panic(const char * str) {
    fputs(str, stderr);
    exit(1);
}

/**
 * @brief panics with the error message, terminating the program
 * @details This function never returns. It writes to `stderr` and exits
 *          with an error code of `1`. This function calls `printf` to
 *          allow message formatting
 * 
 * @param str The error message displayed on termination
 */
void panicf(const char * fmt, ...) __attribute__((noreturn));

void panicf(const char * fmt, ...) {
    va_list args;
    va_start(args, fmt);

    vfprintf(stderr, fmt, args);

    exit(1);
}


result.h

```
#include
#include

#include "panic.h"

/**
* @file result.h
* Provides an alternative to exceptions, whose semantics are slightly better than exceptions
*
* C does not support exceptions. Many C functions instead return a status code
* reporting what happened. Alternatively, many C libraries allow a callback to
* be logged in order to report a detailed description of any errors that
* occur

Solution

It's very heavy on macros, but that's inevitable when we provide functions to extend the language in this way. Users don't have to look at the full gory details anyway, and the user interface looks good. I like the consistent prefix, which makes it very easy to see at a glance that they are related.

It would be instructive to show a function that accepts a result type, and acts on it (if valid) or propagates the error (otherwise), in the same way that NaN values propagate through arithmetic. There might even be a case for another macro, perhaps like

#define RESULT_RETURN_IF_ERR(type, val, result_type)    \
    do {                                                \
        if (RESULT_IS_ERR(type)(val))                   \
            return RESULT_ERR(result_type)(val.err);    \
    } while (0)


The panic() function could probably be replaced at call sites by panicf("%s", message). It might be an idea to add newline to the printing (especially as the messages used in the code are not full lines in themselves).

Perhaps also consider printing the type via %s formatting, to slightly reduce code bloat (so all the expansions of RESULT_UNWRAP() and RESULT_UNWRAP_ERR share the same pair of format strings). The slight performance penalty is trivial, and in a path that shouldn't be exercised in a well-written program.

#define RESULT_DEFINE(type) \
    ⋮
    type RESULT_UNWRAP(type)(const RESULT(type) * res) {                \
        if (RESULT_IS_ERR(type)(res))                                   \
            panicf("Attempted to unwrap empty Result of type "          \
                   "%s. Instead had error: %s", #type, res->err);       \
        return res->value;                                              \
    } \
    ⋮
    const char * RESULT_UNWRAP_ERR(type)(const RESULT(type) * res) {    \
        if (RESULT_IS_OK(type)(res))                                    \
            panic("Result was not an error; type: %s", #type);          \
        return res->err;                                                \
    }

Code Snippets

#define RESULT_RETURN_IF_ERR(type, val, result_type)    \
    do {                                                \
        if (RESULT_IS_ERR(type)(val))                   \
            return RESULT_ERR(result_type)(val.err);    \
    } while (0)
#define RESULT_DEFINE(type) \
    ⋮
    type RESULT_UNWRAP(type)(const RESULT(type) * res) {                \
        if (RESULT_IS_ERR(type)(res))                                   \
            panicf("Attempted to unwrap empty Result of type "          \
                   "%s. Instead had error: %s", #type, res->err);       \
        return res->value;                                              \
    } \
    ⋮
    const char * RESULT_UNWRAP_ERR(type)(const RESULT(type) * res) {    \
        if (RESULT_IS_OK(type)(res))                                    \
            panic("Result was not an error; type: %s", #type);          \
        return res->err;                                                \
    }

Context

StackExchange Code Review Q#140231, answer score: 2

Revisions (0)

No revisions yet.