Skip to content

danieljharris/Cpp-Result

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

Result

A lightweight single-header C++ Result<T> implementation built around std::variant, inspired by Rust-style result handling.

This library provides:

  • Ok<T> for successful operations
  • Warn<T> for non-fatal issues
  • Error for failures
  • Helper macros like TRY(...)
  • Optional value semantics
  • Structured logging support
  • Zero exceptions design philosophy

Overview Example

Result<Database> initialiseDatabase() {
    Database db;

    if (!db.connect()) {
        return Error("Unable to connect to database server"); // Returns error and logs message
    }

    return Ok(move(db));
}

Result<> insertUser() {
    Database db = TRY(initialiseDatabase()); // Macro to return if Error

    if (!db.addUser("Daniel")) {
        return Error("Failed to insert user into database");
    }

    return Ok();
}

Features

  • Single-header library
  • No exceptions required
  • Strongly typed result handling
  • Supports warnings separately from errors
  • std::visit compatible
  • Automatic message formatting via fmt
  • Logging integration via spdlog
  • Implicit result conversion between compatible Result<U> and Result<T>
  • Convenience macros for early returns

Installation

Simply copy ipc_result.h into your project.

Requirements

  • C++17 or newer
  • spdlog
  • fmt (normally already bundled with spdlog)

Example using CMake:

find_package(spdlog REQUIRED)

target_link_libraries(your_target PRIVATE spdlog::spdlog)

Logging Dependency

This implementation currently uses:

  • spdlog for logging
  • fmt for formatting

However, the logging calls are intentionally isolated and minimal, making it easy to switch to another logging library.

You can replace:

spdlog::warn(...)
spdlog::error(...)
spdlog::critical(...)

with your preferred logger.


Core Concepts

The library uses three result states:

Type Meaning
Ok<T> Successful operation
Warn<T> Successful operation with warning
Error Failure

Basic Usage

Returning Success

Result<int> getValue() {
    return Ok(42);
}

Returning Warning

Result<int> getValue() {
    return Warn(
        42,
        "Value loaded from fallback cache"
    );
}

Returning Error

Result<int> getValue() {
    return Error(
        "Failed to load configuration"
    );
}

TRY Macro

The TRY(...) macro simplifies propagating failures upward.

Instead of manually checking every result:

Result<int> result = otherFunc();

if (!result.hasValue()) {
    return result;
}

int value = *result;

You can simply write:

int value = TRY(otherFunc());

If otherFunc() returns an error or empty value, the current function immediately returns.


Realistic Examples

Returning From a Function

Result<int> parsePort() {
    return Ok(8080);
}

Result<int> startServer() {
    int port = TRY(parsePort());

    return Ok(port);
}

Returning Directly

Result<int> getPort() {
    return Ok(8080);
}

Result<int> startServer() {
    return TRY(getPort());
}

Using TRY_VOID

Useful for void functions that still need failure propagation.

Result<int> initialiseDatabase() {
    return Ok(1);
}

void bootSystem() {
    int db = TRY_VOID(initialiseDatabase());

    spdlog::info("Database ready: {}", db);
}

TRY_OK Example

TRY_OK(...) only checks whether the result is an error.

It allows empty successful states.

Result<> initialiseLogging() {
    return Ok();
}

Result<> startApplication() {
    TRY_OK(initialiseLogging());

    return Ok();
}

TRY_OR Example

Return a custom fallback value if the expression fails.

Result<int> getNumber() {
    return Error("Failed");
}

int getNumberOrMinusOne() {
    return TRY_OR(getNumber(), -1);
}

Checking State

Result<int> result = getValue();

if (result.isOk()) {
}

if (result.isWarn()) {
}

if (result.isError()) {
}

Extracting Values

Result<int> result = getValue();

if (result.hasValue()) {
    int value = *result;
}

Extracting Messages

if (auto msg = result.message()) {
    std::cout << *msg << std::endl;
}

std::visit Support

The underlying std::variant can be accessed using .raw().

std::visit(overloaded{
    [](const Ok<int>& ok) {
        std::cout << "Success";
    },
    [](const Warn<int>& warn) {
        std::cout << "Warning";
    },
    [](const Error& err) {
        std::cout << "Error";
    }
}, result.raw());

Design Philosophy

This library intentionally avoids exceptions.

Instead:

  • Function outcomes are explicit in return types
  • Failures propagate naturally
  • Warnings are first-class citizens
  • Logging occurs automatically
  • Optional values prevent accidental invalid access

Notes

  • operator* assumes the result contains a value
  • Invalid dereference attempts terminate the application
  • operator-> returns nullptr if no value exists
  • Warn<T> may optionally contain:
    • a value
    • a warning message
    • both

License

MIT License (recommended)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages