Result Library 1.0
Elegant error handling in C
Loading...
Searching...
No Matches
Result Library

Introduction

Wave goodbye to output parameters, sentinel values, and endless null checking! Embrace clean, efficient error handling today by encapsulating operations that may succeed or fail in a type-safe way.

  • Boost Performance Fewer pointer dereferences; rely on optimized single return values
  • Simple API Handle success and failure scenarios with just a handful of C macros
  • Streamlined Error Handling Reduce the chances of incorrect or inconsistent error handling
  • Safe Execution Enforce valid states, eliminating risks like dangling pointers or stale data
  • Enhanced Readability Reduce boilerplate code to make your code easier to understand
  • Functional Style Avoid side effects and encourage pure, composable functions
  • Lightweight Keep your project slim with no extra dependencies
  • Open Source Enjoy transparent, permissive Apache 2 licensing
  • Header-Only C Library Compatible with C23 standard and modern compilers
Remarks
This library provides a cleaner, safer, and more modern approach to error handling by combining function result and error into a unified return value. It enforces correct usage at the call site, reduces the risk of bugs, and leads to more maintainable and extensible code.

TL;DR

Not a fan of reading long docs? No worries! Tune in to Deep Dive, a podcast generated by NetbookLM. In just a few minutes, you'll get the essential details and a fun intro to what this library can do for you!

Deep Dive Podcast
Deep Dive into the Result Library

Results in a Nutshell

Result objects represent the outcome of an operation, removing the need for output parameters, sentinel values, and null checking. Operations that succeed produce results encapsulating a success value; operations that fail produce results with a failure value. Success and failure can be represented by whatever types make the most sense for each operation.

Let's use a pet store example to show how this library can simplify your code.

Start by defining some data types to represent pets.

// Pet status in the store
typedef enum pet_status {AVAILABLE, PENDING, SOLD} pet_status;
// Represents a pet
typedef struct pet {int id; const char *name; pet_status status;} *Pet;
// Convenience macros
#define PET_ID(pet) (pet)->id
#define PET_NAME(pet) (pet)->name
#define PET_STATUS(pet) (pet)->status
// Pet error codes
typedef enum pet_error {OK, PET_NOT_FOUND, PET_NOT_AVAILABLE, PET_ALREADY_SOLD} pet_error;

Next, let's say you have an array of pets acting as your "database".

// Available pets in the store
static struct pet pets[] = {
{.id = 0, .name = "Rocky", .status = AVAILABLE},
{.id = 1, .name = "Garfield", .status = PENDING},
{.id = 2, .name = "Rantanplan", .status = SOLD}
};
Note
You'll use a function, find_pet(id), to retrieve pets by their ID. We'll skip its implementation for now.

Suppose you need to write a function to get a pet's status.

Warning
This works... until someone passes an invalid ID. If find_pet returns NULL, your code could crash unexpectedly.

To fix this, you refactor the function to return an error code and use an output parameter to "return" the pet status.

It's safer, but also clunky.

Warning
What if the output pointer is NULL? Should you return a new error code? Use assert? It's starting to feel messy.

Instead of juggling pointers and error codes, you should return a Result object.

Remarks
The result encapsulates both success (pet status) and failure (error code) in one clean package. The caller immediately knows the function can fail, and you've eliminated the need for an output pointer. This is simpler, safer, and much easier to read.

Encouraged, you refactor find_pet to return a failed result instead of NULL when a pet isn't found. Now, get_pet_status can rely on find_pet to handle errors and focus on the happy path.

Note
And just like that, your code becomes robust and maintainable. No more mysterious crashes, no awkward pointer checks. Just clean, elegant error handling.

With Results, handling success and failure feels natural, leaving you free to focus on the fun parts of coding.

Getting Started

Adding Results to Your Project

This library consists of one header file only. All you need to do is copy result.h into your project, and include it.

#include <result.h>
A header-only C library to handle errors elegantly.

Since it's a header-only library, there is no library code to link against.

Result library is also available in the Conan Center index repository, and the vcpkg package repository.

Remarks
If you're using Conan, you can add Results to your build file:
[requires]
resultlib/1.0.0
And if you're using vcpkg, you can install the port:
vcpkg install resultlib
Finally, include resultlib/result.h file in your source code.

Result Types and Variables

  • RESULT_STRUCT Declares a result struct with a default tag and the supplied success and failure types.
    RESULT_STRUCT(pet_status, pet_error);
  • RESULT Returns the type specifier for results with the supplied success and failure type names.
    RESULT(pet_status, pet_error) result;

Creating Result Objects

  • RESULT_SUCCESS Initializes a new successful result containing the supplied value.
    RESULT(pet_status, pet_error) result = RESULT_SUCCESS(AVAILABLE);
  • RESULT_FAILURE Initializes a new failed result containing the supplied value.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);

Basic Usage

Checking Success or Failure

  • RESULT_HAS_SUCCESS Checks if a result contains a success value.
    RESULT(pet_status, pet_error) result = RESULT_SUCCESS(AVAILABLE);
    bool has_success = RESULT_HAS_SUCCESS(result);
    assert(has_success == true);
  • RESULT_HAS_FAILURE Checks if a result contains a failure value.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);
    bool has_failure = RESULT_HAS_FAILURE(result);
    assert(has_failure == true);

Unwrapping Values

  • RESULT_USE_SUCCESS Returns a result's success value.
    RESULT(pet_status, pet_error) result = RESULT_SUCCESS(AVAILABLE);
    pet_status value = RESULT_USE_SUCCESS(result);
    assert(value == AVAILABLE);
  • RESULT_USE_FAILURE Returns a result's failure value.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);
    pet_error value = RESULT_USE_FAILURE(result);
    assert(value == PET_NOT_FOUND);
  • RESULT_GET_SUCCESS Returns a result's success value as a possibly-null pointer.
    RESULT(pet_status, pet_error) result = RESULT_SUCCESS(AVAILABLE);
    const pet_status *value = RESULT_GET_SUCCESS(result);
    assert(*value == AVAILABLE);
  • RESULT_GET_FAILURE Returns a result's failure value as a possibly-null pointer.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);
    const pet_error *value = RESULT_GET_FAILURE(result);
    assert(*value == PET_NOT_FOUND);
  • RESULT_OR_ELSE Returns a result's success value, or the supplied one.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_ALREADY_SOLD);
    pet_status value = RESULT_OR_ELSE(result, PENDING);
    assert(value == PENDING);
  • RESULT_OR_ELSE_MAP Returns a result's success value, or maps its failure value.
    #define ERROR_TO_STATUS(x) x == PET_ALREADY_SOLD ? SOLD : PENDING
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_ALREADY_SOLD);
    pet_status value = RESULT_OR_ELSE_MAP(result, ERROR_TO_STATUS);
    assert(value == SOLD);

Conditional Actions

  • RESULT_IF_SUCCESS Performs the supplied action with a successful result's value.
    RESULT(int, pet_error) result = RESULT_SUCCESS(123);
    side_effect = 0;
    RESULT_IF_SUCCESS(result, set_side_effect);
    assert(side_effect == 123);
  • RESULT_IF_FAILURE Performs the supplied action with a failed result's value.
    RESULT(int, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);
    last_error = OK;
    RESULT_IF_FAILURE(result, log_error);
    assert(last_error == PET_NOT_FOUND);
  • RESULT_IF_SUCCESS_OR_ELSE Performs either of the supplied actions with a result's value.
    RESULT(int, pet_error) result = RESULT_FAILURE(PET_NOT_FOUND);
    side_effect = 0;
    last_error = OK;
    RESULT_IF_SUCCESS_OR_ELSE(result, set_side_effect, log_error);
    assert(side_effect == 0);
    assert(last_error == PET_NOT_FOUND);

Advanced Usage

Screening Results

  • RESULT_FILTER Conditionally transforms a successful result into a failed one.
    struct pet sold = {.status = SOLD};
    RESULT(Pet, pet_error) result = RESULT_SUCCESS(&sold);
    RESULT(Pet, pet_error) filtered = RESULT_FILTER(result, is_available, PET_NOT_AVAILABLE);
    assert(RESULT_USE_FAILURE(filtered) == PET_NOT_AVAILABLE);
  • RESULT_FILTER_MAP Conditionally transforms a successful result into a failed one, mapping its success value.
    #define PET_TO_ERROR(pet) (PET_STATUS(pet) == SOLD ? PET_ALREADY_SOLD : PET_NOT_AVAILABLE)
    struct pet sold = {.status = SOLD};
    RESULT(Pet, pet_error) result = RESULT_SUCCESS(&sold);
    RESULT(Pet, pet_error) filtered = RESULT_FILTER_MAP(result, is_available, PET_TO_ERROR);
    assert(RESULT_USE_FAILURE(filtered) == PET_ALREADY_SOLD);
  • RESULT_RECOVER Conditionally transforms a failed result into a successful one.
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_AVAILABLE);
    RESULT(pet_status, pet_error) recovered = RESULT_RECOVER(result, is_not_available, SOLD);
    assert(RESULT_USE_SUCCESS(recovered) == SOLD);
  • RESULT_RECOVER_MAP Conditionally transforms a failed result into a successful one, mapping its failure value.
    #define ERROR_TO_STATUS(x) x == PET_ALREADY_SOLD ? SOLD : PENDING
    RESULT(pet_status, pet_error) result = RESULT_FAILURE(PET_NOT_AVAILABLE);
    RESULT(pet_status, pet_error) recovered = RESULT_RECOVER_MAP(result, is_not_available, ERROR_TO_STATUS);
    assert(RESULT_USE_SUCCESS(recovered) == PENDING);

Transforming Values

Additional Info

Compatibility

Results rely on modern C features such as designated initializers, compound literals, and typeof.

Releases

This library adheres to Semantic Versioning. All notable changes for each version are documented in a change log.

Head over to GitHub for the latest release.

Latest Release

Source Code

The source code is available on GitHub.

Fork me on GitHub