Skip to content

Commit 39769cc

Browse files
committed
BResult improvements
This is a draft commit trying to make some improvements to the `BResult` class to make it more usable. Adds a `util::Result` class with the following improvements (and implements `BResult` in terms of it so existing code doesn't have to change): - Supports returning error strings and error information at the same time. This functionality is needed by the `LoadChainstate` function in #25308 or any function that can fail in multiple ways which need to be handled differently. And it means Result class is compatible with rust-style error handling and pattern matching, unlike BResult. - Makes Result class provide same operators and methods as std::optional, so it doesn't require calling less familiar `HasRes`/`GetObj`/`ReleaseObj` methods, and so error reporting functionality can be easily added or dropped from existing code by switching between util::Result and std::optional. - Supports `Result<void>` return values, so it is possible to return error reporting information from functions in a uniform way even if they don't have other return values. - Supports `Result<bilingual_str>` return values. Drops ambiguous and potentially misleading `BResult` constructor that treats any bilingual string as an error, and any other type as a non-error. Adds `util::Error` constructor so errors have to be explicitly constructed and not any `bilingual_str` will be turned into one. - Puts `src/util/` code in the `util::` namespace so naming reflects code organization and it is obvious where the class is coming from. Drops "B" from name because it is undocumented what it stands for (bilingual?) - Adds unit tests.
1 parent 1d89fc6 commit 39769cc

File tree

5 files changed

+224
-22
lines changed

5 files changed

+224
-22
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ libbitcoin_util_a_SOURCES = \
676676
util/moneystr.cpp \
677677
util/rbf.cpp \
678678
util/readwritefile.cpp \
679+
util/result.cpp \
679680
util/settings.cpp \
680681
util/thread.cpp \
681682
util/threadnames.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ BITCOIN_TESTS =\
118118
test/raii_event_tests.cpp \
119119
test/random_tests.cpp \
120120
test/rest_tests.cpp \
121+
test/result_tests.cpp \
121122
test/reverselock_tests.cpp \
122123
test/rpc_tests.cpp \
123124
test/sanity_tests.cpp \

src/test/result_tests.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/result.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
9+
BOOST_AUTO_TEST_SUITE(result_tests)
10+
11+
util::Result<void> VoidSuccessFn()
12+
{
13+
return {};
14+
}
15+
16+
util::Result<void> VoidFailFn()
17+
{
18+
return {util::Error{}, Untranslated("void fail")};
19+
}
20+
21+
util::Result<int> IntSuccessFn(int ret)
22+
{
23+
return {ret};
24+
}
25+
26+
util::Result<int> IntFailFn()
27+
{
28+
return {util::Error{}, Untranslated("int fail")};
29+
}
30+
31+
enum FnStatus { SUCCESS, ERR1, ERR2 };
32+
33+
util::Result<FnStatus> StatusSuccessFn(FnStatus ret)
34+
{
35+
return {ret};
36+
}
37+
38+
util::Result<FnStatus> StatusFailFn(FnStatus ret)
39+
{
40+
return {util::Error{}, Untranslated("status fail"), ret};
41+
}
42+
43+
util::Result<int> ChainedFailFn(FnStatus arg, int ret)
44+
{
45+
return {util::ErrorChain{}, Untranslated("chained fail"), StatusFailFn(arg), ret};
46+
}
47+
48+
template<typename T>
49+
void ExpectSuccess(const util::Result<T>& result)
50+
{
51+
BOOST_CHECK(result);
52+
}
53+
54+
template<typename T>
55+
void ExpectFail(const util::Result<T>& result, bilingual_str str)
56+
{
57+
BOOST_CHECK(!result);
58+
BOOST_CHECK_EQUAL(ErrorDescription(result).original, str.original);
59+
BOOST_CHECK_EQUAL(ErrorDescription(result).translated, str.translated);
60+
}
61+
62+
template<typename T, typename... Args>
63+
void ExpectValue(const util::Result<T>& result, bool has_value, Args&&... args)
64+
{
65+
BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...});
66+
BOOST_CHECK_EQUAL(result.has_value(), has_value);
67+
BOOST_CHECK_EQUAL(&result.value(), &*result);
68+
}
69+
70+
template<typename T, typename... Args>
71+
void ExpectSuccessValue(const util::Result<T>& result, Args&&... args)
72+
{
73+
ExpectSuccess(result);
74+
ExpectValue(result, true, std::forward<Args>(args)...);
75+
}
76+
77+
template<typename T, typename... Args>
78+
void ExpectFailValue(const util::Result<T>& result, bilingual_str str, Args&&... args)
79+
{
80+
ExpectFail(result, str);
81+
ExpectValue(result, false, std::forward<Args>(args)...);
82+
}
83+
84+
BOOST_AUTO_TEST_CASE(check_returned)
85+
{
86+
ExpectSuccess(VoidSuccessFn());
87+
ExpectFail(VoidFailFn(), Untranslated("void fail"));
88+
ExpectSuccessValue(IntSuccessFn(5), 5);
89+
ExpectFail(IntFailFn(), Untranslated("int fail"));
90+
ExpectSuccessValue(StatusSuccessFn(SUCCESS), SUCCESS);
91+
ExpectFailValue(StatusFailFn(ERR2), Untranslated("status fail"), ERR2);
92+
ExpectFailValue(ChainedFailFn(ERR1, 5), Untranslated("status fail, chained fail"), 5);
93+
}
94+
95+
BOOST_AUTO_TEST_CASE(check_dereference_operators)
96+
{
97+
util::Result<std::pair<int, std::string>> mutable_result;
98+
const auto& const_result{mutable_result};
99+
mutable_result.value() = {1, "23"};
100+
BOOST_CHECK_EQUAL(mutable_result->first, 1);
101+
BOOST_CHECK_EQUAL(const_result->second, "23");
102+
(*mutable_result).first = 5;
103+
BOOST_CHECK_EQUAL((*const_result).first, 5);
104+
}
105+
106+
BOOST_AUTO_TEST_SUITE_END()

src/util/result.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/result.h>
6+
#include <util/string.h>
7+
8+
namespace util {
9+
bilingual_str ErrorDescription(const Result<void>& result)
10+
{
11+
return Join(result.GetErrors(), Untranslated(", "));
12+
}
13+
} // namespace util
14+

src/util/result.h

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,117 @@
66
#define BITCOIN_UTIL_RESULT_H
77

88
#include <util/translation.h>
9-
#include <variant>
9+
#include <optional>
1010

11-
/*
12-
* 'BResult' is a generic class useful for wrapping a return object
13-
* (in case of success) or propagating the error cause.
14-
*/
15-
template<class T>
16-
class BResult {
17-
private:
18-
std::variant<bilingual_str, T> m_variant;
11+
namespace util {
12+
13+
//! Function result type intended for high-level functions that return error and
14+
//! warning strings in addition to normal result types.
15+
//!
16+
//! The Result<T> class is meant to be a drop-in replacement for
17+
//! std::optional<T> except it has additional methods to return error and
18+
//! warning strings for error reporting. Also, unlike std::optional, in order to
19+
//! support error handling in cases where callees need to pass additional
20+
//! information about failures back to callers, Result<T> objects can always be
21+
//! dereferenced regardless of the function's success or failure.
22+
//!
23+
//! This class is not intended to be used by low-level functions that do not
24+
//! return error or warning strings. These functions should use plain
25+
//! std::optional or std::variant types instead.
26+
//!
27+
//! See unit tests in result_tests.cpp for example usages.
28+
template<typename T>
29+
class Result;
30+
31+
//! Tag types for result constructors.
32+
struct Error{};
33+
struct ErrorChain{};
34+
35+
//! Result<void> specialization. Only returns errors and warnings, no values.
36+
template<>
37+
class Result<void>
38+
{
39+
protected:
40+
std::vector<bilingual_str> m_errors;
41+
std::vector<bilingual_str> m_warnings;
1942

2043
public:
21-
BResult() : m_variant(Untranslated("")) {}
22-
BResult(const T& _obj) : m_variant(_obj) {}
23-
BResult(const bilingual_str& error) : m_variant(error) {}
44+
//! Success case constructor.
45+
Result() = default;
2446

25-
/* Whether the function succeeded or not */
26-
bool HasRes() const { return std::holds_alternative<T>(m_variant); }
47+
//! Error case constructor for a single error.
48+
Result(Error, bilingual_str error)
49+
{
50+
m_errors.emplace_back(std::move(error));
51+
}
2752

28-
/* In case of success, the result object */
29-
const T& GetObj() const {
30-
assert(HasRes());
31-
return std::get<T>(m_variant);
53+
//! Error case constructor for a chained error.
54+
Result(ErrorChain, bilingual_str error, Result<void>&& previous) : m_errors{std::move(previous.m_errors)}, m_warnings{std::move(previous.m_warnings)}
55+
{
56+
m_errors.emplace_back(std::move(error));
3257
}
3358

34-
/* In case of failure, the error cause */
35-
const bilingual_str& GetError() const {
36-
assert(!HasRes());
37-
return std::get<bilingual_str>(m_variant);
59+
//! Success check.
60+
operator bool() const { return m_errors.empty(); }
61+
62+
//! Error retrieval.
63+
const std::vector<bilingual_str>& GetErrors() const { return m_errors; }
64+
std::tuple<const std::vector<bilingual_str>&, const std::vector<bilingual_str>&> GetErrorsAndWarnings() const { return {m_errors, m_warnings}; }
65+
};
66+
67+
template<typename T>
68+
class Result : public Result<void>
69+
{
70+
protected:
71+
T m_result;
72+
73+
public:
74+
//! Constructors that forward to the base class and pass additional arguments to m_result.
75+
template<typename... Args>
76+
Result(Args&&... args) : m_result{std::forward<Args>(args)...} {}
77+
template<typename Str, typename...Args>
78+
Result(Error, Str&& str, Args&&... args) : Result<void>{Error{}, std::forward<Str>(str)}, m_result{std::forward<Args>(args)...} {};
79+
template<typename Str, typename Prev, typename...Args>
80+
Result(ErrorChain, Str&& str, Prev&& prev, Args&&... args) : Result<void>{ErrorChain{}, std::forward<Str>(str), std::forward<Prev>(prev)}, m_result{std::forward<Args>(args)...} {};
81+
82+
//! std::optional methods, so Result<T> can be easily swapped for
83+
//! std::optional<T> to add error reporting to existing code or remove it if
84+
//! it is no longer needed.
85+
bool has_value() const { return m_errors.empty(); }
86+
const T& value() const { return m_result; }
87+
T& value() { return m_result; }
88+
template<typename U> T value_or(const U& default_value) const
89+
{
90+
return has_value() ? value() : default_value;
3891
}
92+
const T* operator->() const { return &value(); }
93+
const T& operator*() const { return value(); }
94+
T* operator->() { return &value(); }
95+
T& operator*() { return value(); }
96+
};
97+
98+
//! Helper method to retrieve a simple error string from Result<T> or
99+
//! Result<void>.
100+
bilingual_str ErrorDescription(const Result<void>& result);
101+
} // namespace util
39102

103+
/**
104+
* Backwards-compatible interface for util::Result class. New code should prefer
105+
* util::Result class which supports returning error information along with
106+
* result information and supports returing `void` and `bilingual_str` results.
107+
*/
108+
template<class T>
109+
class BResult {
110+
private:
111+
util::Result<std::optional<T>> m_result;
112+
113+
public:
114+
BResult() : m_result{util::Error{}, Untranslated("")} {};
115+
BResult(const T& value) : m_result{value} {}
116+
BResult(const bilingual_str& error) : m_result{util::Error{}, error} {}
117+
bool HasRes() const { return m_result.has_value(); }
118+
const T& GetObj() const { assert(HasRes()); return *m_result.value(); }
119+
const bilingual_str& GetError() const { assert(!HasRes()); return m_result.GetErrors().back(); }
40120
explicit operator bool() const { return HasRes(); }
41121
};
42122

0 commit comments

Comments
 (0)