Common System 0.2.0
Common interfaces and patterns for system integration
Loading...
Searching...
No Matches
Tutorial: Result<T> Error Handling

Goal

Learn how to use kcenon::common::Result<T> for safe, explicit error handling without exceptions. By the end of this tutorial you will be able to create, chain, and consume Result values idiomatically.

Prerequisites

  • C++20 compiler (GCC 13+, Clang 17+, MSVC 2022+)
  • common_system installed (see Main Page for installation)
  • Familiarity with basic template syntax

Step 1: Creating Result values

A Result<T> carries either a successful value of type T or an error_info. Construct one of each:

#include <string>
using namespace kcenon::common;
Result<int> parse_port(const std::string& s) {
try {
int port = std::stoi(s);
if (port < 1 || port > 65535) {
return make_error<int>(error_code::invalid_argument,
"Port out of range: " + s);
}
return make_ok(port);
} catch (const std::exception& e) {
return make_error<int>(error_code::parse_failed, e.what());
}
}
Result type for error handling with member function support.
Definition core.cppm:165
Core interfaces.
Definition adapter.h:21
Umbrella header for Result<T> type and related utilities.
Note
Prefer make_ok / make_error factory helpers over direct construction — they document intent and avoid accidental slicing.

Step 2: Consuming Result values

Use is_ok() / is_error() to branch, or pattern-match with std::visit:

void start_server(const std::string& port_str) {
auto result = parse_port(port_str);
if (result.is_ok()) {
int port = result.value();
std::cout << "Starting server on port " << port << std::endl;
// ... bind socket, begin listening ...
} else {
const auto& err = result.error();
std::cerr << "Failed: [" << static_cast<int>(err.code)
<< "] " << err.message << std::endl;
}
}
const error_info & error() const
Get error reference.
Definition core.h:405

Step 3: Chaining operations

Multiple fallible operations can be chained with .and_then() so that the first error short-circuits the rest — no nested if pyramids required.

Result<config> load_config(const std::string& path) {
return read_file(path)
.and_then([](const std::string& text) { return parse_json(text); })
.and_then([](const json_value& json) { return validate_schema(json); })
.and_then([](const json_value& json) { return build_config(json); });
}
auto and_then(F &&func) const -> decltype(func(std::declval< T >()))
Map a function that returns a Result (flatMap/bind)
Definition core.h:430
Result< std::string > read_file(const std::string &path)

Each step receives the previous step's success value; if any returns an error, the whole chain becomes that error.

Common Mistakes

  • Ignoring the return value. Result has [[nodiscard]] — respect it. Silently dropping a result discards its error, defeating the whole point of using it.
  • Calling .value() without checking. .value() on an error Result throws (or aborts, depending on configuration). Use .is_ok() first, or prefer .value_or(default).
  • Mixing exceptions and Result. Choose one style per module. Wrapping exception-throwing code at API boundaries is fine; sprinkling throws inside Result chains defeats the explicitness.

Next Steps