/* * \brief Utility for passing return values * \author Norman Feske * \date 2021-11-09 */ /* * Copyright (C) 2021 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #ifndef _INCLUDE__UTIL__ATTEMPT_H_ #define _INCLUDE__UTIL__ATTEMPT_H_ #include namespace Genode { template struct [[nodiscard]] Attempt; template struct [[nodiscard]] Unique_attempt; /** * Type used for results with no return value but error conditions */ struct Ok { }; } /** * Option type for return values * * The 'Attempt' type addresses the C++ limitation to only a single return * value, which makes it difficult to propagate error information in addition * to an actual return value from a called function back to the caller. Hence, * errors have to be propagated as exceptions, results have to be returned via * out parameters, or error codes have to be encoded in the form of magic * values. Each of these approaches create its own set of robustness problems. * * An 'Attempt' represents the result of a function call that is either a * meaningful value or an error code, but never both. The result value and * error are distinct types. To consume the return value of a call, the caller * needs to specify two functors, one for handing the value if the value exists * (the call was successful), and one for handling the error value if the call * failed. Thereby the use of an 'Attempt' return type reinforces the explicit * handling of all possible error conditions at the caller site. */ template class Genode::Attempt { private: bool _ok; RESULT _result { }; ERROR _error { }; public: Attempt(RESULT result) : _ok(true), _result(result) { } Attempt(ERROR error) : _ok(false), _error(error) { } Attempt(Attempt const &) = default; Attempt &operator = (Attempt const &) = default; template RET convert(auto const &access_fn, auto const &fail_fn) const { return _ok ? RET { access_fn(_result) } : RET { fail_fn(_error) }; } void with_result(auto const &access_fn, auto const &fail_fn) const { _ok ? access_fn(_result) : fail_fn(_error); } void with_error(auto const &fail_fn) const { if (!_ok) fail_fn(_error); } bool operator == (ERROR const &rhs) const { return failed() && (_error == rhs); } bool operator == (RESULT const &rhs) const { return ok() && (_result == rhs); } [[nodiscard]] bool ok() const { return _ok; } [[nodiscard]] bool failed() const { return !_ok; } void print(Output &out) const { with_result([&] (RESULT const &result) { Genode::print(out, result); }, [&] (ERROR const &error) { Genode::print(out, error); }); } }; /** * Base type for the result of constrained RAM allocations * * The type is a unique pointer to the allocated RAM while also holding * allocation-error information, following the conventions of 'Attempt'. */ template class Genode::Unique_attempt : Noncopyable { private: Constructible _result { }; ERROR _error { }; protected: void _construct(auto &&... args) { _result.construct(args...); } void _destruct (ERROR e) { _result.destruct(); _error = e; } public: Unique_attempt(auto &&... args) { _result.construct(args...); } Unique_attempt(ERROR error) { _destruct(error); } template RET convert(auto const &access_fn, auto const &fail_fn) { return _result.constructed() ? RET { access_fn(*_result) } : RET { fail_fn(_error) }; } template RET convert(auto const &access_fn, auto const &fail_fn) const { return _result.constructed() ? RET { access_fn(*_result) } : RET { fail_fn(_error) }; } void with_result(auto const &access_fn, auto const &fail_fn) { _result.constructed() ? access_fn(*_result) : fail_fn(_error); } void with_result(auto const &access_fn, auto const &fail_fn) const { _result.constructed() ? access_fn(*_result) : fail_fn(_error); } void with_error(auto const &fail_fn) const { if (!_result.constructed()) fail_fn(_error); } bool operator == (ERROR const &rhs) const { return failed() && (_error == rhs); } [[nodiscard]] bool ok() const { return _result.constructed(); } [[nodiscard]] bool failed() const { return !_result.constructed(); } void print(auto &out) const { with_result([&] (RESULT const &result) { Genode::print(out, result); }, [&] (ERROR const &error) { Genode::print(out, error); }); } }; #endif /* _INCLUDE__UTIL__ATTEMPT_H_ */