Skip to content

Commit

Permalink
std::variant result type
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanofsky committed Jul 13, 2022
1 parent 1d89fc6 commit c29d300
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ BITCOIN_TESTS =\
test/raii_event_tests.cpp \
test/random_tests.cpp \
test/rest_tests.cpp \
test/result_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
Expand Down
81 changes: 81 additions & 0 deletions src/test/result_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <util/result.h>

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(result_tests)

util::Result<void> VoidSuccessFn()
{
return {};
}

util::Result<void> VoidFailFn()
{
return util::Error(Untranslated("void fail"));
}

util::Result<int> IntSuccessFn(int ret)
{
return {ret};
}

util::Result<int> IntFailFn()
{
return util::Error(Untranslated("int fail"));
}

enum MultiErr { ERR1, ERR2 };

util::Result<int, MultiErr> MultiSuccessFn(int ret)
{
return {ret};
}

util::Result<int, MultiErr> MultiFailFn(MultiErr err)
{
return util::Error(Untranslated("multi fail"), err);
}

template<typename T, typename E>
void ExpectSuccess(const util::Result<T, E>& result)
{
BOOST_CHECK(result.has_value());
}

template<typename T, typename E, typename... Args>
void ExpectSuccessValue(const util::Result<T, E>& result, Args&&... args)
{
ExpectSuccess(result);
BOOST_CHECK(result.value() == T{std::forward<Args>(args)...});
}

template<typename T, typename E>
void ExpectFail(const util::Result<T, E>& result, bilingual_str str)
{
BOOST_CHECK(!result.has_value());
BOOST_CHECK(result.ErrorDescription().original == str.original);
BOOST_CHECK(result.ErrorDescription().translated == str.translated);
}

template<typename T, typename E, typename... Args>
void ExpectFailValue(const util::Result<T, E>& result, bilingual_str str, Args&&... args)
{
ExpectFail(result, str);
BOOST_CHECK(result.Error() == E{std::forward<Args>(args)...});
}

BOOST_AUTO_TEST_CASE(util_datadir)
{
ExpectSuccess(VoidSuccessFn());
ExpectFail(VoidFailFn(), Untranslated("void fail"));
ExpectSuccessValue(IntSuccessFn(5), 5);
ExpectFail(IntFailFn(), Untranslated("int fail"));
ExpectSuccessValue(MultiSuccessFn(5), 5);
ExpectFailValue(MultiFailFn(ERR2), Untranslated("int fail"), ERR2);
}

BOOST_AUTO_TEST_SUITE_END()
113 changes: 90 additions & 23 deletions src/util/result.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,103 @@
#include <util/translation.h>
#include <variant>

/*
* 'BResult' is a generic class useful for wrapping a return object
* (in case of success) or propagating the error cause.
*/
template<class T>
class BResult {
private:
std::variant<bilingual_str, T> m_variant;
namespace util {

template<typename E>
struct _ErrorType;

template<>
struct _ErrorType<void>
{
template<typename Str, typename... Args>
_ErrorType(Str&& str, Args&&... args) : str{std::forward<Str>(str)} {}

bilingual_str str;
};

template<typename E>
struct _ErrorType : _ErrorType<void>
{
template<typename Str, typename... Args>
_ErrorType(Str&& _str, Args&&... args) : _ErrorType<void>{std::forward<Str>(_str)}, error(std::forward<Args>(args)...) {}

E error;
};

template<typename... Args>
struct _ErrorArgs {
std::tuple<Args&...> args;
};

template<typename... Args>
_ErrorArgs<Args...> Error(Args&&... args)
{
return _ErrorArgs<Args...>{{args...}};
}

/**
* Function return type similar to std::optional<T> except it can return error
* data and an error string description if the function fails. This is intended
* for high level functions that can produce error strings. Low level functions
* that just need results and non-string errors should use std::optional or
* std::variant directly instead.
*
* See unit tests in result_tests.cpp for example usages.
*/
template<typename T, typename E = void>
class Result {
private:
using SuccessType = std::conditional_t<std::is_same<T, void>::value, std::monostate, T>;
using ErrorType = _ErrorType<E>;
using ResultType = std::variant<SuccessType, ErrorType>;
ResultType m_result;
public:
BResult() : m_variant(Untranslated("")) {}
BResult(const T& _obj) : m_variant(_obj) {}
BResult(const bilingual_str& error) : m_variant(error) {}
template<typename... Args>
Result(Args&&... args) : m_result{std::in_place_index_t<0>(), std::forward<Args>(args)...} {}

/* Whether the function succeeded or not */
bool HasRes() const { return std::holds_alternative<T>(m_variant); }
template<typename... Args>
Result(_ErrorArgs<Args...> error) : m_result{std::make_from_tuple<ErrorType>(error.args)} {}

/* In case of success, the result object */
const T& GetObj() const {
assert(HasRes());
return std::get<T>(m_variant);
// Methods for std::optional compatibility in success case.
bool has_value() const { return m_result.index() == 0; }
const SuccessType& value() const { assert(has_value()); return std::get<0>(m_result); }
SuccessType& value() { assert(has_value()); return std::get<0>(m_result); }
template<typename U> SuccessType value_or(const U& default_value) const
{
return has_value() ? value() : default_value;
}
operator bool() const { return has_value(); }
const SuccessType* operator->() const { return &value(); }
const SuccessType& operator*() const { return value(); }
SuccessType* operator->() { return &value(); }
SuccessType& operator*() { return value(); }

/* In case of failure, the error cause */
const bilingual_str& GetError() const {
assert(!HasRes());
return std::get<bilingual_str>(m_variant);
}
// Methods for getting failure information in error case.
auto& Error() { assert(!has_value()); return std::get<1>(m_result).error; }
const auto& Error() const { assert(!has_value()); return std::get<1>(m_result).error; }
const bilingual_str& ErrorDescription() const { assert(!has_value()); return std::get<1>(m_result).str; }
};

} // namespace util

explicit operator bool() const { return HasRes(); }
/**
* Backwards-compatible interface for util::Result class. New code should prefer
* util::Result class which supports returning error information along with
* result information and supports returing `void` and `bilingual_str` results.
*/
template<class T>
class BResult {
private:
util::Result<T> m_result;

public:
BResult() : m_result{util::Error(Untranslated(""))} {};
BResult(const T& value) : m_result{value} {}
BResult(const bilingual_str& error) : m_result{util::Error(error)} {}
bool HasRes() const { return m_result.has_value(); }
const T& GetObj() const { return m_result.value(); }
const bilingual_str& GetError() const { return m_result.ErrorDescription(); }
explicit operator bool() const { return m_result.has_value(); }
};

#endif // BITCOIN_UTIL_RESULT_H

0 comments on commit c29d300

Please sign in to comment.