C++ Logo

std-proposals

Advanced search

[std-proposals] Abbreviating error handling

From: shortanemoia_at <shortanemoia_at_[hidden]>
Date: Sun, 27 Mar 2022 18:57:30 +0000
There is an error handling pattern that appears repeatedly in many C++ codebases:

(1)
​auto maybe_result_or_error = a_function_that_may_fail();
if (maybe_result_or_error.has_error())
return maybe_result_or_error.error();
auto result = maybe_result_or_error.result();

Here, we call a function that may fail, in which case we get an object explaining or signaling the error.
We check if the result has an error, in which case we don't want to continue executing the current function, and return the error, propagating it.

Sometimes, it also looks like:
(2.1)
auto maybe_error = a_function_that_may_fail();
if (maybe_error.has_error())
return maybe.error();
OR
(2.2)
auto maybe_error = a_function_that_may_fail();
if (maybe_error.has_error())
return;

Here, the function we call doesn't return anything unless it fails, in which case we get an object signaling the error.
As in the first case, in many cases the programmer will want the function to return because we
didn't get what we wanted from that function (be it a connected socket, an open file, a memory allocation, etc).
We return the error, propagating it.

In other codebases, it might look like this:

(3)
auto* maybe_ptr = a_function_that_may_return_null();
if (maybe_ptr == nullptr)
return;
auto result = *maybe_ptr;

There is a common pattern here. We get a return value from a function, check if it's valid,
in which case we unwrap that result, or if it's invalid, in which case we do something, often returning the error to propagate it.

I propose a new syntax feature:

auto result = try a_function_that_may_fail(); // for examples (1) and (3)
AND
try a_function_that_may_fail(); // for examples (2.1) and (2.2)

This translates internally to the respective examples, and serves the purpose of simplifying a common pattern that, just
like for loops with iterators, is very common to see in projects that do not make use of exceptions. It moves boilerplate
away from the code in a zero-cost way.
It should return the error value if the return value of the function it appears in is not void and is implicitly constructible from the
error type. The approach to implementing this, in order to make it a soft feature like structured-binding, could be with

special member function names like has_error(), result() and error(), akin to how structured-binding uses get() for tuples (e).
Types like std::expected have these methods, and could be used with this new syntax sugar.

A macro version is already in use in some projects (see references (a) and (b) for uses and (c) for the macro implementation),
or is even built-in in languages like Rust (d).

I look forward to your feedback,
Cesar Torres.

References:

a. https://github.com/SerenityOS/serenity/blob/master/Documentation/Patterns.md#try-error-handling
b. https://github.com/SerenityOS/serenity/blob/f7f14d52e0beeb61b133e556e3e5b841e570d364/Userland/Applications/ImageViewer/main.cpp#L42
c. https://github.com/SerenityOS/serenity/blob/f7f14d52e0beeb61b133e556e3e5b841e570d364/AK/Try.h
d. https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
e. https://en.cppreference.com/w/cpp/language/structured_binding

Received on 2022-03-27 18:57:36