![]() |
Arene Base
Fundamental Utilities For Safety Critical C++
|
Facilities for handling a result type similar to std::expected from C++23, or Result from Rust that encapsulates either a return value or an error code.
The public header is
The Bazel target is
A C++ function can only return a single return value. When designing APIs which might fail to perform the desired task, this leads developers to generally use one of a few workarounds:
All of these approaches have significant drawbacks:
const-correct code, because out parameters str inherently non-const. This also means it cannot be used with a type which should otherwise be immutable once created.The arene::base::result type attempts to address these drawbacks by providing a standard vocabulary type to return which unambiguously represents two possible states: either the function successfully performed its action and returned a value, or it failed and returned an error. The API design allows writing natural, declarative code to check and handle errors, while allowing straightforward composition and const-correctness. It is similar in concept to Rust's Result or C++23's std::expected.
The arene::base::result<V,E> type is always in one of two states:
V.E.The constraints on the types suitable for use as V and E are minimal: E cannot be void, and nether V nor E can be references, c-v qualified, or c-style arrays.
For example, the following might represent the result of opening a file
And the open function would look like:
When the open API performs its task successfully, it populates the value channel of the result using the arene::base::value_result helper:
When the open API is unable to perform its task successfully, it populates the error channel of the result using the arene::base::error_result helper:
The value_result and error_result helpers are simple reference wrappers that tell the result type itself which channel to populate. They also help make it declarative to future maintainers of the code what channel was intended to be populated at a given return location. They generally do not incur a performance penalty, but values should be moved into them when they are non-trivial.
The caller of open then can use the result of open as follows:
Efficiently replacing the content of a result after it has been instantiated can be accomplished via the arene::base::result::emplace API:
It is also possible to query for equality against the held value/error directly through arene::base::result::has_value(const value_type&) and arene::base::result::has_error(const error_type&):
For full API details, please see the API reference.
arene::base::result is a checked type. This means that ARENE_PRECONDITION is used on any access method which returns a reference, such as result::value or result::error, to validate that the channel being accessed is actually populated.
Beyond simple procedural access of the error and value channels, there are advanced techniques which can be used in some cases to further reduce boilerplate and write more declarative code.
arene::base::result::and_then(F&& value_handler) is a monadic API which consumes an invocable with the signature result<U, error_type>(value_type), where value_type is compatible with the const/reference qualification of the result it is invoked on, error_type is the error type of the result it is invoked on and U is any valid value type (including void). If the result's value channel is populated, this handler is invoked and its result returned. Otherwise, a result<U, error_type> created from the content of the error channel is returned.
and_then is used to chain together result producing operations which share a common error type. An example usage opening a file:
After this statement, maybe_file will contain either a valid instance of arene::base::file_handle in the value channel, or it will contain an instance of arene::base::error_code representing either the failure to open the file or the failure to open the directory.
value_result is not a result, it is convertible to a result. and_then requires the handler to return a result.arene::base::result::or_else(F&& error_handler) is a monadic API which consumes an invocable with the signature result<value_type, U>(error_type), where error_type is compatible with the const/reference qualification of the result it is invoked on, value_type is the value type of the result it is invoked on and U is any valid error type. If the result's error channel is populated, this handler is invoked and its result returned. Otherwise, a result<value_type, U> created from the content of the value channel is returned.
or_else is generally used to implement "fallback" logic for an operation, where the fallback logic itself may fail or may have variant behavior depending on how the non-default operation failed. For example, opening a configuration file and falling back to some default configuration file if the given path doesn't exist could look like:
After this statement, maybe_configuration is result<file_handle, error_code>, and will contain either an opened file handle in the value channel produced from ether the requested or default configuration names, or it will contain an error_code that represents the failure to open either file.
error_result is not a result, it is convertible to a result. or_else requires the handler to return a result.arene::base::result::transform(F&& value_handler) is a functional API which consumes an invocable with the signature U(value_type), where value_type is compatible with the const/reference qualification of the result it is invoked on and U is any valid value type (including void). If the result's value channel is populated, this handler is invoked and a result<U, error_type> is created from the return, where error_type is the error type of the result it is invoked on. Otherwise, a result<U, error_type> created from the content of the error channel is returned.
transform is generally used to dispatch to functions which operate on the value channel and cannot themselves fail. An example is printing the next entry in an arene::base::directory_handle:
In this example, maybe_processed_entry will be result<void, arene::base::error_code>. If next_entry()'s return had its value channel populated, print_entry is called and maybe_processed_entry will have its value channel populated. Otherwise maybe_processed_entry will contain the error_code from next_entry().
arene::base::result::transform_error(F&& value_handler) is a functional API which consumes a invocable with the signature U(error_type), where error_type is cref compatible with the cref qualification of the result it is invoked on and U is any valid error type. If the result's error channel is populated, this handler is invoked and a result<value_type, U> is created from the return, where value_type is the value type of the result it is invoked on. Otherwise, a result<value_type, U> created from the content of the value channel is returned.
transform_error is generally used for situations where some unconditional processing of the error type returned by an API is needed, and that processing cannot fail. For example, interposing logging the error returned by attempting to open a file might look like:
expand_resultThe arene::base::expand_result API can be used to access the content of an arene::base::result in a similar manner to accessing a std::variant using a visitor. The example from basic usage rewritten to use expand_result would be implemented as follows: