refactor: Add util::Result failure values, multiple error and warning messages #25665

pull ryanofsky wants to merge 5 commits into bitcoin:master from ryanofsky:pr/bresult2 changing 9 files +689 −146
  1. ryanofsky commented at 12:46 pm on July 21, 2022: contributor

    Add util::Result support for returning more error information and make use of it in LoadChainstate method as an initial application. Followup PR #25722 uses it more broadly to return errors and warnings from wallet loading functions as well.

    This change adds two major features to the result class:

    • For better error handling, adds the ability to return a value on failure, not just a value on success. This is a key missing feature that makes the result class not useful for functions like LoadChainstate() which produce different errors that need to be handled differently 1.
    • For better error reporting, adds the ability to return warning messages and multiple errors, not just a single error string. This provides a way for functions to report errors and warnings in a standard way, and simplifies interfaces:

      0-virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
      1+virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name) = 0;
      
      0-std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
      1+util::Result<std::unique_ptr<WalletDatabase>, DatabaseError> MakeDatabase(const fs::path& path, const DatabaseOptions& options);
      

    This change also makes some more minor improvements:

    • Smaller type size. util::Result<int> is 16 bytes, and util::Result<void> is 8 bytes. Previously both were 72 bytes.

    • Broader type compatibility. Supports noncopyable and nonmovable success and error types.

    Alternatives & design notes

    The main goal for the util::Result class is to provide a standard way for functions to report error and warnings strings. The interface tries to make it easy to provide detailed error feedback to end users, without cluttering code or making it harder to return other result information. There have been multiple iterations of the design using different internal representations and providing different capabilities:

    Representation (approximate) Notes
    1 tuple<T,optional<bilingual_str>> Original #25218 implementation in da98fd2efc1a6b9c6e876cf3e227a8c2e9a10827. Good capabilities, but larger type size. Supports error standardized error reporting and customized error handling with failure values.
    2 variant<T,bilingual_str> Final #25218 implementation in 7a45c33d1f8a758850cf8e7bd6ad508939ba5c0d. No support for returning failure values so not useful in many cases.
    3 variant<monostate,T,F> Pending #25601 implementation that allows choosing whether to use standardized error reporting or return custom failure values, but doesn’t support having both at the same time, like approaches (1), (4), (5), (6) do.
    4 tuple<variant<T,F>,bilingual_str> Original #25608 implementation in c29d3008de9314dd463ed08e8bff39fe55324498. Basically the same as (1) except it uses separate success and failure types instead of the same type. Using separate success and failure types makes the result class a little less flexible but can help avoid some ambiguity and inconsistent result states.
    5 tuple<T,optional<bilingual_str>> Final #25608 implementation in dd91f294206ac87b213d23bb76656a0a5f0f4781. Essentially the same as original implementation (1) except has some usability improvements.
    6 tuple<T,unique_ptr<tuple<F,bilingual_str>> Pending #25665 implementation (this PR). Supports returning failure values, uses separate success and failure types to avoid ambiguity, uses unique_ptr to reduce result type size, and avoids heap allocation in the happy path.

    Prior discussions & history

    • furszy introduced a BResult class providing a standard error reporting mechanism in #25218. It was renamed to util::Result in #25721 but kept the same representation and capabilities.

    • MarcoFalke suggested using BResult for the LoadChainstate function in #25308 (comment). Inability to use BResult there due to lack of support for failure values led to initial work on #25608.

    • w0xlt wrote a StructuredResult class in #25308 adding the ability to return failure values but sacrificing standard error reporting, which led to more work on #25608.

    • martinus suggested a space optimization in #25608 (comment) that also made it easier to support distinct failure & success types, leading to #25665 as a replacement for #25608.


    1. Ability to return error values was actually present in the original implementation of #25218, but unfortunately removed in later iterations. ↩︎

  2. in src/bench/wallet_loading.cpp:49 in 6a06a7c3ad outdated
    45@@ -46,10 +46,10 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
    46 static void AddTx(CWallet& wallet)
    47 {
    48     const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
    49-    assert(dest.HasRes());
    50+    assert(dest);
    


    maflcko commented at 1:05 pm on July 21, 2022:

    This pull changes both the interface, as well as the internal representation. I wonder if changing the interface and renames can be done separate from the restructuring.

    Hopefully those would be uncontroversial and could be reviewed easier/faster, so that less silent merge conflicts arise as the class gets used more and more. Also, less code will need to be changes if the interface changes are made earlier rather than later.


    ryanofsky commented at 1:12 pm on July 21, 2022:

    This pull changes both the interface, as well as the internal representation. I wonder if changing the interface and renames can be done separate from the restructuring.

    Only the third commit drops BResult usages, so the answer should be yes. IMO std::optional has a nice interface and it is a shame to reinvent it with stranger HasRes GetObj ReleaseObj methods.


    maflcko commented at 1:14 pm on July 21, 2022:
    With separate I meant separate pulls :sweat_smile:

    ryanofsky commented at 1:20 pm on July 21, 2022:

    With separate I meant separate pulls sweat_smile

    Oh sorry, I thought you were asking literally if it is possible


    maflcko commented at 1:23 pm on July 22, 2022:

    I think the stuff that can be split out:

    • Makes Result class provide same operators and methods as std::optional
    • 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?)
    • 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.

    ryanofsky commented at 2:22 pm on July 22, 2022:
    I think these things are already split out. The first two commits do not change the interface or naming or functionality of BResult in any way. If you want me to move the last commit to another, PR I can do that.

    maflcko commented at 2:27 pm on July 22, 2022:
    Yeah, I think that makes sense, so that we can first figure out the interface, and then start using it more widely, not the other way round.

    ryanofsky commented at 9:58 pm on July 26, 2022:

    re: #25665 (review)

    Yeah, I think that makes sense, so that we can first figure out the interface, and then start using it more widely, not the other way round.

    Thanks, moved to #25721 now and rewrote PR description

  3. ryanofsky commented at 1:18 pm on July 21, 2022: contributor
    Draft PR since I want to add a commit taking advantages of ability to return chain results and return warnings.
  4. ryanofsky force-pushed on Jul 21, 2022
  5. DrahtBot commented at 6:24 pm on July 21, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Approach ACK hebasto
    Stale ACK w0xlt, stickies-v, hernanmarino, jonatack, achow101, maflcko, TheCharlatan

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #30364 (logging: Replace LogError and LogWarning with LogAlert by ryanofsky)
    • #30344 (kernel: remove mempool_persist by theuni)
    • #30342 (kernel, logging: Pass Logger instances to kernel objects by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  6. ryanofsky force-pushed on Jul 21, 2022
  7. ryanofsky force-pushed on Jul 21, 2022
  8. DrahtBot added the label Needs rebase on Jul 22, 2022
  9. ryanofsky force-pushed on Jul 22, 2022
  10. DrahtBot removed the label Needs rebase on Jul 22, 2022
  11. ryanofsky force-pushed on Jul 25, 2022
  12. ryanofsky force-pushed on Jul 26, 2022
  13. ryanofsky force-pushed on Jul 26, 2022
  14. ryanofsky force-pushed on Jul 26, 2022
  15. ryanofsky renamed this:
    BResult improvements, allow returning separate value on failure
    refactor: Add util::Result class and use it in LoadChainstate
    on Jul 27, 2022
  16. DrahtBot added the label Refactoring on Jul 27, 2022
  17. ryanofsky force-pushed on Jul 27, 2022
  18. ryanofsky marked this as ready for review on Jul 27, 2022
  19. ryanofsky commented at 7:32 pm on July 27, 2022: contributor
    Moved out of draft, since result interface is more complete now and there’s more code using it
  20. maflcko commented at 7:44 pm on July 27, 2022: member

    As said on the previous pull, I’d prefer if the order of the changes was inversed: First, change the existing interface methods and names, then change the internal class implementation. Otherwise we’ll end up with the odd state of two classes that do the same thing but have a different name and people will continue to add more used of the “deprecated” class. I’d also find it easier to review an incremental diff than having a full separate implementation. But no strong opinion, if you and the other reviewers prefer it as-is now.

    Edit: My comment was #25665 (review) and I just realized that it was ambiguous and could be interpreted the opposite of what I meant.

  21. ryanofsky commented at 8:13 pm on July 27, 2022: contributor
    Thanks, Marco! I will just tweak #25721 to be a standalone PR instead of depending on this PR
  22. ryanofsky referenced this in commit 2aa408b4cc on Jul 27, 2022
  23. ryanofsky referenced this in commit e71b858bc0 on Jul 27, 2022
  24. ryanofsky commented at 9:39 pm on July 27, 2022: contributor

    Thanks, Marco! I will just tweak #25721 to be a standalone PR instead of depending on this PR

    This is done now

  25. ryanofsky referenced this in commit 42f4f7d126 on Jul 27, 2022
  26. ryanofsky referenced this in commit e0289b1cdf on Aug 1, 2022
  27. ryanofsky referenced this in commit 3262acf70a on Aug 2, 2022
  28. ryanofsky referenced this in commit 1e1f5ca8ac on Aug 2, 2022
  29. ryanofsky referenced this in commit 6777df621a on Aug 2, 2022
  30. ryanofsky referenced this in commit c654ec55f8 on Aug 2, 2022
  31. ryanofsky referenced this in commit 7b249b3a16 on Aug 2, 2022
  32. ryanofsky referenced this in commit a23cca56c0 on Aug 3, 2022
  33. ryanofsky referenced this in commit 0e50848064 on Aug 3, 2022
  34. maflcko referenced this in commit 006740b6f6 on Aug 5, 2022
  35. DrahtBot added the label Needs rebase on Aug 5, 2022
  36. ryanofsky renamed this:
    refactor: Add util::Result class and use it in LoadChainstate
    refactor: Add util::Result failure values, multiple error and warning messages
    on Aug 5, 2022
  37. ryanofsky force-pushed on Aug 5, 2022
  38. ryanofsky commented at 6:48 pm on August 5, 2022: contributor
    Rebased c2dc8a8a747d639acfa4a26db2c61c25b6f82571 -> 590bc615a3120a8f11712220546f9654058b82f0 (pr/bresult2.10 -> pr/bresult2.11, compare) due to conflicts with #25721
  39. DrahtBot removed the label Needs rebase on Aug 5, 2022
  40. in src/util/result.cpp:16 in 590bc615a3 outdated
    11+    return Join(errors, Untranslated(" "));
    12+}
    13+
    14+bilingual_str ErrorString(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings)
    15+{
    16+    bilingual_str result = ErrorString(errors);
    


    stickies-v commented at 6:12 pm on August 11, 2022:

    nit:

    0    bilingual_str result{ErrorString(errors)};
    

    ryanofsky commented at 8:37 pm on August 22, 2022:

    re: #25665 (review)

    nit:

    Thanks, used suggestion.

  41. in src/util/result.h:17 in 590bc615a3 outdated
     7@@ -8,16 +8,133 @@
     8 #include <attributes.h>
     9 #include <util/translation.h>
    10 
    11+#include <memory>
    12+#include <optional>
    13+#include <tuple>
    14 #include <variant>
    15+#include <vector>
    


    stickies-v commented at 6:33 pm on August 11, 2022:
    I’m not sure why iwyu doesn’t catch it, but shouldn’t forward, types and utility also be included here?

    ryanofsky commented at 8:37 pm on August 22, 2022:

    re: #25665 (review)

    I’m not sure why iwyu doesn’t catch it, but shouldn’t forward, types and utility also be included here?

    Thanks, added utility. (I think the others are nonstandard.) IWYU might not catch references to code in templates that aren’t instantiated, so I’m not too surprised it didn’t add this.

  42. in src/util/result.h:97 in 590bc615a3 outdated
    78+
    79+    //! Error retrieval.
    80+    template <typename _F = F>
    81+    std::enable_if_t<!std::is_same<_F, void>::value, const _F&> GetFailure() const { assert(!*this); return *m_info->failure; }
    82+    const std::vector<bilingual_str>& GetErrors() const { return m_info ? m_info->errors : EMPTY_LIST; }
    83+    const std::vector<bilingual_str>& GetWarnings() const { return m_info ? m_info->warnings : EMPTY_LIST; }
    


    maflcko commented at 11:18 am on August 12, 2022:
    Could probably make sense to split this up and use it there: #25616 (review)

    ryanofsky commented at 3:31 pm on August 15, 2022:

    re: #25665 (review)

    Could probably make sense to split this up and use it there: #25616 (comment)

    Is the suggestion to add the GetErrors() method in this PR, but delay adding the GetWarnings() method until followup PR #25722? I could do that, but I don’t think it would simplify this PR very much (it would remove a few lines) and it would add more churn to result implementation/documentation/unit tests review


    maflcko commented at 3:34 pm on August 15, 2022:
    No, I was wondering if GetWarnings could be added by itself in the other pull, maybe conflicting with this one, but otherwise unrelated from this one.

    maflcko commented at 3:35 pm on August 15, 2022:
    This would allow reviewers to just look at the GetWarnings implementation, API, and proposed usage, without having to think about union/void/... etc.

    ryanofsky commented at 4:41 pm on August 15, 2022:

    re: #25665 (review)

    This would allow reviewers to just look at the GetWarnings implementation, API, and proposed usage, without having to think about union/void/... etc.

    Oh. I guess there could be a third PR independent of this PR and #25722 that adds a std::vector<bilingual_str> m_warnings; field to util::Result and AddWarning()/GetWarnings() methods. I don’t think it would be as useful as you might be thinking without template constructors that can move errors & warnings from one result object to another, though.

    Maybe if you are interested you could open a PR that does this? I’d be happy to review it. I’m just not sure exactly what you want to do here because the goal of #25722 is a lot broader. Not just dropping std::vector<bilingual_str>& warnings output arguments but also dropping DatabaseStatus& status output arguments and returning errors and warnings directly from nested calls.


    maflcko commented at 3:35 pm on August 29, 2022:

    The thing is that you are basically adding a ton of dead code in this pull. bitcoind compiles fine without it, so I might be picky, but my preference would be to only add the code when it is needed. This also makes it easier for reviewers because they can think about actual use cases and don’t have to imagine them from the unit tests.

      0diff --git a/src/util/result.cpp b/src/util/result.cpp
      1index 9526b5b785..199a6579d7 100644
      2--- a/src/util/result.cpp
      3+++ b/src/util/result.cpp
      4@@ -3,26 +3,8 @@
      5 // file COPYING or https://www.opensource.org/licenses/mit-license.php.
      6 
      7 #include <util/result.h>
      8-#include <util/string.h>
      9 
     10 namespace util {
     11 namespace detail {
     12-bilingual_str JoinMessages(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str> warnings)
     13-{
     14-    bilingual_str result;
     15-    for (const auto& messages : {errors, warnings}) {
     16-        for (const auto& message : messages) {
     17-            if (!result.empty()) result += Untranslated(" ");
     18-            result += message;
     19-        }
     20-    }
     21-    return result;
     22-}
     23-
     24-void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest)
     25-{
     26-    dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
     27-    src.clear();
     28-}
     29 } // namespace detail
     30 } // namespace util
     31diff --git a/src/util/result.h b/src/util/result.h
     32index 32fe40763f..8eb5b552d3 100644
     33--- a/src/util/result.h
     34+++ b/src/util/result.h
     35@@ -17,21 +17,11 @@
     36 
     37 namespace util {
     38 namespace detail {
     39-//! Empty string list
     40-const std::vector<bilingual_str> EMPTY_LIST{};
     41-
     42-//! Helper function to join messages in space separated string.
     43-bilingual_str JoinMessages(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str> warnings);
     44-
     45-//! Helper function to move messages from one vector to another.
     46-void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest);
     47-
     48-//! Error information only allocated if there are errors or warnings.
     49+//! Error information only allocated if there is an error.
     50 template <typename F>
     51 struct ErrorInfo {
     52     std::optional<std::conditional_t<std::is_same<F, void>::value, std::monostate, F>> failure{};
     53-    std::vector<bilingual_str> errors;
     54-    std::vector<bilingual_str> warnings;
     55+    bilingual_str error;
     56 };
     57 
     58 //! Result base class which is inherited by Result<T, F>.
     59@@ -66,25 +56,7 @@ protected:
     60 public:
     61     void AddError(bilingual_str error)
     62     {
     63-        if (!error.empty()) Info().errors.emplace_back(std::move(error));
     64-    }
     65-
     66-    void AddWarning(bilingual_str warning)
     67-    {
     68-        if (!warning.empty()) Info().warnings.emplace_back(std::move(warning));
     69-    }
     70-
     71-    //! Operator moving warning and error messages from other result to this.
     72-    //! Only moves message strings, does not change success or failure values of
     73-    //! either Result object.
     74-    template<typename O>
     75-    O&& operator<<(O&& other)
     76-    {
     77-        if (other.m_info) {
     78-            if (!other.m_info->errors.empty()) MoveMessages(other.m_info->errors, Info().errors);
     79-            if (!other.m_info->warnings.empty()) MoveMessages(other.m_info->warnings, Info().warnings);
     80-        }
     81-        return std::forward<O>(other);
     82+        Info().error = std::move(error);
     83     }
     84 
     85     //! Success check.
     86@@ -93,8 +65,7 @@ public:
     87     //! Error retrieval.
     88     template <typename _F = F>
     89     std::enable_if_t<!std::is_same<_F, void>::value, const _F&> GetFailure() const { assert(!*this); return *m_info->failure; }
     90-    const std::vector<bilingual_str>& GetErrors() const { return m_info ? m_info->errors : EMPTY_LIST; }
     91-    const std::vector<bilingual_str>& GetWarnings() const { return m_info ? m_info->warnings : EMPTY_LIST; }
     92+    bilingual_str GetError() const { return m_info ? m_info->error : bilingual_str{}; }
     93 };
     94 
     95 //! Result base class for T value type. Holds value and provides accessor methods.
     96@@ -145,9 +116,6 @@ public:
     97 struct Error {
     98     bilingual_str message;
     99 };
    100-struct Warning {
    101-    bilingual_str message;
    102-};
    103 
    104 //! The util::Result class provides a standard way for functions to return error
    105 //! and warning strings in addition to optional result values.
    106@@ -190,23 +158,11 @@ protected:
    107         }, std::forward<Args>(args)...);
    108     }
    109 
    110-    template <typename Fn, typename... Args>
    111-    void Construct(const Fn& fn, Warning warning, Args&&... args)
    112-    {
    113-        this->AddWarning(std::move(warning.message));
    114-        Construct(fn, std::forward<Args>(args)...);
    115-    }
    116-
    117-    template <typename Fn, typename OT, typename OF, typename... Args>
    118-    void Construct(const Fn& fn, Result<OT, OF>&& other, Args&&... args)
    119-    {
    120-        *this << other;
    121-        Construct(fn, std::forward<Args>(args)...);
    122-    }
    123-
    124     void MoveConstruct(Result& other)
    125     {
    126-        *this << other;
    127+        if (other.m_info) {
    128+            this->AddError(std::move(other.m_info->error));
    129+        }
    130         if (other) this->MoveValue(other); else this->Info().failure = std::move(other.m_info->failure);
    131     }
    132 
    133@@ -233,7 +189,7 @@ public:
    134 //! are present. More complicated applications should use GetErrors() and
    135 //! GetWarning() methods directly.
    136 template <typename T, typename F>
    137-bilingual_str ErrorString(const Result<T, F>& result) { return detail::JoinMessages(result.GetErrors(), result.GetWarnings()); }
    138+bilingual_str ErrorString(const Result<T, F>& result) { return result.GetError(); }
    139 } // namespace util
    140 
    141 #endif // BITCOIN_UTIL_RESULT_H
    

    ryanofsky commented at 4:15 pm on August 29, 2022:

    re: #25665 (review)

    The thing is that you are basically adding a ton of dead code in this pull

    Interesting and thanks for the diff! Now I understand what you are suggesting, and I think I’d be ok with making that change. I wasn’t really looking at that code as dead because I’m immediately using it in the next pull #25722 and also using it in unit tests this PR. I also didn’t consider it to be a ton of code, since the code in your diff is just getter/setter functions that provide access to errors/warnings variables. But no objection to splitting it off into another pull.


    maflcko commented at 6:22 am on August 30, 2022:
    Yeah, I don’t feel too strong as well. Though, if you keep it, it would be good to fixup the typo: #25665 (review)

    ryanofsky commented at 6:25 pm on August 30, 2022:

    re: #25665 (review)

    Yeah, I don’t feel too strong as well. Though, if you keep it, it would be good to fixup the typo: #25665 (comment)

    Ok, I’m hedging right now by moving multiple error & warning support into a separate commit but not a separate PR. I maybe have a slight preference for just squashing the commits again to reduce churn, but I’m also fine with keeping separate commits, or moving the commit to another PR if one of those alternatives seems better, so let me know!

    Also fixed the typo

  43. in src/util/result.h:365 in 590bc615a3 outdated
    238+            this->m_info.reset(new detail::ErrorInfo<F>{.errors = std::move(other.m_info->errors),
    239+                                                        .warnings = std::move(other.m_info->warnings)});
    240+        } else if (other.m_info && this->m_info) {
    241+            detail::MoveElements(other.m_info->errors, this->m_info->errors);
    242+            detail::MoveElements(other.m_info->warnings, this->m_info->warnings);
    243+        }
    


    Riahiamirreza commented at 4:42 pm on August 13, 2022:
    What is the difference between when the this->m_info is true and when it is false? While as far as I understand in both cases the this->m_info is destroyed and replaced by other.m_info. Am I correct? So why bother to check other.m_info && !this->m_info and other.m_info && this->m_info?

    ryanofsky commented at 8:02 pm on August 13, 2022:

    What is the difference between when the this->m_info is true and when it is false?

    The implementation is optimized for the “happy path” where a success value is set and there is no failure value and no error or warning message strings. In the happy path case, m_info is null and no allocation is needed. Otherwise, if there has been any kind of error or warning m_info will be non-null.

    While as far as I understand in both cases the this->m_info is destroyed and replaced by other.m_info. Am I correct?

    No that’s not really correct. As the comment “Operator moving warning and error messages from one result to another” says, only warning and error strings are moved from other to *this. The success and value statuses of other and *this objects are unchanged, and the success and failure values of both objects (if any) are also unchanged. Existing warning and error strings in *this are also not affected, the new errors and warnings just get appended and don’t replace existing one.

    So why bother to check other.m_info && !this->m_info and other.m_info && this->m_info?

    I’m not sure what other code you might expect to see here, but in both cases this is just moving other.m_info->errors strings to this->m_info->errors and moving other.m_info->warnings strings to this->m_info->warnings while not deleting any existing strings and not changing other.m_info->failure and this->m_info->failure values.


    Riahiamirreza commented at 8:49 am on August 15, 2022:
    Thanks for your explanation. My wrong assumption was that by replacing the warnings and errors the this is completely like the other, so why checking whether this->m_info is null or not. But as you mentioned the success and value statuses remained unchanged.
  44. in src/util/result.h:244 in 590bc615a3 outdated
    86+//! Result base class for T value type. Holds value and provides accessor methods.
    87+template <typename T, typename F>
    88+class ResultBase : public ResultBase<void, F>
    89+{
    90+protected:
    91+    union { T m_value; };
    


    Riahiamirreza commented at 8:39 am on August 15, 2022:
    I don’t understand why the m_value is in a union while there is only one member in the union? Would you explain why?

    ryanofsky commented at 12:47 pm on August 15, 2022:

    I don’t understand why the m_value is in a union while there is only one member in the union? Would you explain why?

    I’ll add a code comment, but using a union avoids m_value getting constructor and destructor being called automatically, so in the failure case m_value is never constructed.

  45. Riahiamirreza approved
  46. ryanofsky force-pushed on Aug 15, 2022
  47. ryanofsky commented at 6:16 pm on August 15, 2022: contributor
    Updated 590bc615a3120a8f11712220546f9654058b82f0 -> 65481de0646f21349f24327410e4d7eb5189e5b3 (pr/bresult2.11 -> pr/bresult2.12, compare) just adding some comments to answer review questions
  48. in src/node/chainstate.h:39 in 65481de064 outdated
    42-using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
    43+//! Chainstate load errors. Simple applications can just treat all errors as
    44+//! failures. More complex applications may want to try reindexing in the
    45+//! generic error case, and pass an interrupt callback and exit cleanly in the
    46+//! interrupted case.
    47+enum class ChainstateLoadError { FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED };
    


    dongcarl commented at 6:08 pm on August 16, 2022:

    Perhaps could take the opportunity to make this something like:

    0enum class ChainstateLoadError { FAILURE_TRY_REINDEX, FAILURE_FATAL, INTERRUPTED };
    

    So that it’s more inline with indicating “what to do about the failure”.

    Could also move the “4 types of outcomes” comment up here where it’s clearer.


    ryanofsky commented at 6:31 pm on August 17, 2022:

    re: #25665 (review)

    So that it’s more inline with indicating “what to do about the failure”.

    I agree it’s good to suggest how failures can be handled, so I tried to do that in the comment. But I think if failure has a clear cause it’s best for the error name to describe the cause, especially since not every application will want to handle errors the same way, and bitcoin-qt, bitcoind, and bitcoin-chainstate all handle errors differently.

    I’m also don’t think FAILURE_TRY_REINDEX is an appropriate synonym for FAILURE, since generic failures can happen on any exception, even if reindexing was already requested. Trying to reindex when reindexing fails does not make a lot of sense. I’d be happy to rename FAILURE to FAILURE_GENERIC. But would probably do it in a separate PR to not complicate this one.

    Could also move the “4 types of outcomes” comment up here where it’s clearer.

    Thanks for pointing it out. I think that comment actually does not add much information anymore, and is a little confusing because it is mentioning shutdowns when the check_interrupt callback and INTERRUPTED error could indicate any custom interruption, not just a shutdown.

    I can add more documentation if you think anything is missing, but I think a simple message of “It is fine to treat any error code as a failure and ignore the specific cause” is the most helpful takeaway. Setting interrupt callbacks and trying to reindex are optional enhancements mostly helpful for interactive applications.

  49. in src/node/chainstate.h:52 in 65481de064 outdated
    50@@ -53,9 +51,9 @@ using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
    51  *
    52  *  LoadChainstate returns a (status code, error string) tuple.
    


    AryanJ-NYC commented at 3:10 pm on August 17, 2022:
    This comment is no longer true.

    ryanofsky commented at 5:59 pm on August 17, 2022:

    re: #25665 (review)

    This comment is no longer true.

    Thanks, removed

  50. in src/util/result.h:77 in 65481de064 outdated
    72+        if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
    73+        m_info->warnings.emplace_back(std::move(warning));
    74+    }
    75+
    76+    //! Success check.
    77+    operator bool() const { return !m_info || !m_info->failure; }
    


    sipa commented at 5:21 pm on August 17, 2022:
    Would it be sufficient to use explicit operator bool() const instead here? That would avoid e.g. “(result + 3)” from compiling (and evaluating to 3 or 4).

    ryanofsky commented at 6:00 pm on August 17, 2022:

    re: #25665 (review)

    Would it be sufficient to use explicit operator bool() const instead here? That would avoid e.g. “(result + 3)” from compiling (and evaluating to 3 or 4).

    Good catch, added explicit. This was an unintended regression (operator bool was explicit before this PR)


    hernanmarino commented at 6:22 pm on August 17, 2022:
    +1 to @sipa ’s comment.
  51. ryanofsky force-pushed on Aug 17, 2022
  52. ryanofsky commented at 7:26 pm on August 17, 2022: contributor
    Updated 65481de0646f21349f24327410e4d7eb5189e5b3 -> 9bd10728bada8b04d86f5621ee127713f628a9ad (pr/bresult2.12 -> pr/bresult2.13, compare) with suggestions
  53. hernanmarino commented at 4:38 am on August 18, 2022: contributor
    Tested ACK and Code review ACK. I got a few warnings while compiling though (missing-field-initializers)
  54. ryanofsky commented at 2:18 pm on August 18, 2022: contributor

    Tested ACK and Code review ACK. I got a few warnings while compiling though (missing-field-initializers)

    Thanks for testing! @hernanmarino could you post the warnings, and maybe post your compiler version? I don’t think I’m seeing these and I don’t think they are happening on CI because those builds treat warnings as errors. I’d like to fix this if possible.

  55. in src/util/result.h:227 in 9bd10728ba outdated
    256-{
    257-    return result ? bilingual_str{} : std::get<0>(result.m_variant);
    258-}
    259+//! Helper methods to format error strings.
    260+bilingual_str ErrorString(const std::vector<bilingual_str>& errors);
    261+bilingual_str ErrorString(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings);
    


    stickies-v commented at 1:53 pm on August 19, 2022:
    Do we want to expose these 2 helper methods in the header?

    ryanofsky commented at 7:59 pm on August 23, 2022:

    re: #25665 (review)

    Do we want to expose these 2 helper methods in the header?

    Nope, good point. Moved these to the details namespace.

  56. in src/util/result.h:229 in 9bd10728ba outdated
    258-}
    259+//! Helper methods to format error strings.
    260+bilingual_str ErrorString(const std::vector<bilingual_str>& errors);
    261+bilingual_str ErrorString(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings);
    262+template <typename T, typename F>
    263+bilingual_str ErrorString(const Result<T, F>& result) { return ErrorString(result.GetErrors(), result.GetWarnings()); }
    


    stickies-v commented at 1:54 pm on August 19, 2022:

    What about renaming this function to ErrorWarningString() since at the moment it can contain both? I can see some scenario where people would want to access just the error but not the warning string (and of course it’s just a convenience fn), so that would allow them to create the more narrow ErrorString() later on?

    Perhaps a brief docstring above this fn would be beneficial as well since that’s the one people will mostly use.


    ryanofsky commented at 7:59 pm on August 23, 2022:

    re: #25665 (review)

    What about renaming this function to ErrorWarningString() since at the moment it can contain both? I can see some scenario where people would want to access just the error but not the warning string (and of course it’s just a convenience fn), so that would allow them to create the more narrow ErrorString() later on?

    Added some more documentation about how this should be used. I think a function that just puts errors not warnings in a result message could be a potential footgun, because warnings could be unintentionally dropped if errors and warnings are returned together. For example if code initially doesn’t generate any warnings, then someone adds one not realizing it won’t show up anywhere.


    stickies-v commented at 7:21 pm on August 24, 2022:

    I didn’t think about the footgun, that’s a good point why we probably indeed won’t want to add the other fn later. I still think it’s a bit awkward that the function name doesn’t entirely capture what the function is doing, but I think it’s within reason and may be worth the trade-off for brevity (vs ErrorWarningString(). Not sure what I prefer, so feel free to ignore.

    The new docstring is great, thanks!


    ryanofsky commented at 2:55 pm on August 25, 2022:

    re: #25665 (review)

    I still think it’s a bit awkward that the function name doesn’t entirely capture what the function is doing, but I think it’s within reason and may be worth the trade-off for brevity (vs ErrorWarningString(). Not sure what I prefer, so feel free to ignore.

    Could call it util::MessageString(result) instead of util::ErrorString(result). I think the longer name would be ok too. I was pushing back more against changing the functionality than changing the name.

    I do think any renaming should happen in a separate PR, before or after this one. The function already exists and is called in current code. This PR is backwards compatible just extends the Result API without requiring changes to existing code.


    stickies-v commented at 3:28 pm on August 25, 2022:

    The function already exists and is called in current code.

    Right, I did not consider that. Don’t think it’s worth the extra PR without more demand for it so I’m happy to just leave it at ErrorString until then.

  57. in src/util/result.h:96 in 9bd10728ba outdated
    25+{
    26+    dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
    27+    src.clear();
    28+}
    29+
    30+//! Error information only allocated if there are errors or warnings.
    


    stickies-v commented at 2:02 pm on August 19, 2022:
    nit: I think this docstring is more relevant for m_info, perhaps move it to above its declaration in ResultBase?

    ryanofsky commented at 8:37 pm on August 22, 2022:

    re: #25665 (review)

    nit: I think this docstring is more relevant for m_info, perhaps move it to above its declaration in ResultBase?

    I think it’s relevant here because the struct is pretty big thing to have in a return type, so good to say it won’t typically be needed.

    Also, this may be a stylistic thing, but I tend to prefer class-level documentation that gets at why/how questions, over member level documentation only answers more basic what questions, and can also clutter the class definition.


    stickies-v commented at 11:23 am on August 31, 2022:
    Maybe I’m confused, but I don’t think I agree. Since the optional allocation of an ErrorInfo is a result of how Info() is implemented, not of how the ErrorInfo struct is defined - isn’t that where it should be documented? Unless you’re talking about the optional allocation of failure instead of ErrorInfo, in which case I would agree that this should be explained at this location (and you might want to add another one to info explaining the dynamic allocation of m_info)?

    ryanofsky commented at 2:07 pm on September 1, 2022:

    re: #25665 (review)

    Maybe I’m confused, but I don’t think I agree. Since the optional allocation of an ErrorInfo is a result of how Info() is implemented, not of how the ErrorInfo struct is defined - isn’t that where it should be documented?

    I’m only pushing back against removing this comment here. I’m happy to add more documentation anywhere would would like.

    I think the answer to your question is no, because this isn’t a general purpose API. This is a custom, private struct that is never exposed externally and exists for one purpose. The comment is describing what the purpose is. If I were reading this code and saw this struct, I would be wondering why there is such a heavyweight struct being used in a lightweight result type, why error information is segregated from normal result information, and why a separate struct definition is needed at all instead using normal class members. This comment explains what the purpose of the struct is, why it exists and how it is used, and I think is appropriate documentation for an single-purpose piece of a larger implementation.

  58. in src/util/result.h:104 in 9bd10728ba outdated
    33+    std::optional<std::conditional_t<std::is_same<F, void>::value, std::monostate, F>> failure;
    34+    std::vector<bilingual_str> errors;
    35+    std::vector<bilingual_str> warnings;
    36+};
    37+
    38+//! Result base class which is inherited by Result<T, F>.
    


    stickies-v commented at 2:07 pm on August 19, 2022:

    nit: You could add a bit more info on T, F for quick understanding?

    0//! Result base class which is inherited by Result<T, F>.
    1//! T is the type of the success return value, or void if there is none.
    2//! F is the type of the failure return value, or void if there is none.
    

    ryanofsky commented at 8:38 pm on August 22, 2022:

    re: #25665 (review)

    nit: You could add a bit more info on T, F for quick understanding?

    Added. Nice suggestion!

  59. in src/util/result.h:120 in 9bd10728ba outdated
    54+
    55+public:
    56+    void AddError(bilingual_str error)
    57+    {
    58+        if (error.empty()) return;
    59+        if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
    


    stickies-v commented at 2:30 pm on August 19, 2022:

    This is repeated a few times, what do you think about this:

     0diff --git a/src/util/result.h b/src/util/result.h
     1index 60e0b3db6..c6aa65891 100644
     2--- a/src/util/result.h
     3+++ b/src/util/result.h
     4@@ -56,17 +56,19 @@ public:
     5     void AddError(bilingual_str error)
     6     {
     7         if (error.empty()) return;
     8-        if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
     9+        CheckInitInfo();
    10         m_info->errors.emplace_back(std::move(error));
    11     }
    12 
    13     void AddWarning(bilingual_str warning)
    14     {
    15         if (warning.empty()) return;
    16-        if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
    17+        CheckInitInfo();
    18         m_info->warnings.emplace_back(std::move(warning));
    19     }
    20 
    21+    void CheckInitInfo() { if (!m_info) m_info = std::make_unique<ErrorInfo<F>>(); }
    22+
    23     //! Success check.
    24     explicit operator bool() const { return !m_info || !m_info->failure; }
    25 
    26@@ -159,7 +161,7 @@ protected:
    27     {
    28         this->AddError(std::move(error.message));
    29         Construct([&](auto&&... x) {
    30-            if (!this->m_info) this->m_info = std::make_unique<detail::ErrorInfo<F>>();
    31+            this->CheckInitInfo();
    32             this->m_info->failure.emplace(std::forward<decltype(x)>(x)...);
    33         }, std::forward<Args>(args)...);
    34     }
    

    ryanofsky commented at 8:38 pm on August 22, 2022:

    re: #25665 (review)

    This is repeated a few times, what do you think about this:

    Nice catch! I implemented something similar to what you suggested to dedup


    stickies-v commented at 7:32 pm on August 24, 2022:
    I like your solution, very elegant!
  60. hernanmarino commented at 2:36 pm on August 19, 2022: contributor

    Thanks for testing! @hernanmarino could you post the warnings, and maybe post your compiler version? I don’t think I’m seeing these and I don’t think they are happening on CI because those builds treat warnings as errors. I’d like to fix this if possible.

    Yes, this is one of them (there are a few more , all similar)

     0 In file included from ./interfaces/wallet.h:15,
     1                 from wallet/interfaces.cpp:5:
     2./util/result.h: In instantiation of ‘util::Result<OT, OF>&& util::Result<T, F>::operator<<(util::Result<OT, OF>&&) [with OT = std::unique_ptr<interfaces::Wallet>; OF = void; T = std::unique_ptr<interfaces::Wallet>; F = void]’:
     3
     4./util/result.h:187:15:   required from ‘void util::Result<T, F>::MoveConstruct(util::Result<OT, OF>&) [with OT = std::unique_ptr<interfaces::Wallet>; OF = void; T = std::unique_ptr<interfaces::Wallet>; F = void]’
     5
     6./util/result.h:202:51:   required from ‘util::Result<T, F>::Result(util::Result<OT, OF>&&) [with OT = std::unique_ptr<interfaces::Wallet>; OF = void; T = std::unique_ptr<interfaces::Wallet>; F = void]’
     7
     8wallet/interfaces.cpp:580:16:   required from here
     9
    10./util/result.h:218:32: warning: missing initializer for member ‘util::detail::ErrorInfo<void>::failure’ [-Wmissing-field-initializers]
    11  218 |             this->m_info.reset(new detail::ErrorInfo<F>{.errors = std::move(other.m_info->errors),
    12      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    13  219 |                                                         .warnings = std::move(other.m_info->warnings)});
    14      |                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    I’m using gcc 11.2.0 . If you need them all, just let me know

  61. pablomartin4btc commented at 2:53 pm on August 19, 2022: member

    @ryanofsky, @hernanmarino I also get those warnings.

    My version of g++ is 9.4.0; please let me know if you need any other details.

  62. in src/util/result.h:192 in 9bd10728ba outdated
    111+    }
    112+    template <class U>
    113+    T value_or(U&& default_value) &&
    114+    {
    115+        return has_value() ? std::move(value()) : std::forward<U>(default_value);
    116+    }
    


    stickies-v commented at 3:05 pm on August 19, 2022:
    As part of the public interface, I think these could benefit from a very brief per-method docstring (that probably just mirrors the std::optional docstring)?

    ryanofsky commented at 8:38 pm on August 22, 2022:

    re: #25665 (review)

    As part of the public interface, I think these could benefit from a very brief per-method docstring (that probably just mirrors the std::optional docstring)?

    I’m a little reluctant to change this code here because it is just moving (not new), but would encourage you to submit a separate PR to add more documentation to existing code if it would be helpful. I’m happy to rebase this as other PR are merged.

    I do personally tend not to like documenting individual class members if it’s obvious what they do, and save comments for when there is something non-obvious or higher level to point out, but I wouldn’t have any objection to more detailed documentation.

  63. in src/util/result.h:104 in 9bd10728ba outdated
     99+    friend class ResultBase;
    100+
    101+public:
    102+    //! std::optional methods, so functions returning optional<T> can change to
    103+    //! return Result<T> with minimal changes to existing code, and vice versa.
    104+    bool has_value() const { return *this ? true : false; }
    


    stickies-v commented at 3:24 pm on August 19, 2022:

    nit: slightly more concise and clear on intent imo

    0    bool has_value() const { return bool(*this); }
    

    ryanofsky commented at 8:38 pm on August 22, 2022:

    re: #25665 (review)

    nit: slightly more concise and clear on intent imo

    Ok! I would have defaulted to that but had seen some feedback bool() was not good #25616 (review), #25721 (review) so was trying to avoid it. Switched to bool{} for now.

  64. in src/test/result_tests.cpp:191 in 9bd10728ba outdated
    158+    BOOST_CHECK_EQUAL(result.GetFailure(), F{std::forward<Args>(args)...});
    159 }
    160 
    161 BOOST_AUTO_TEST_CASE(check_returned)
    162 {
    163+    ExpectResult(VoidSuccessFn(), true, {});
    


    stickies-v commented at 3:43 pm on August 19, 2022:
    I think it would be helpful to also test success with a falsy non-void value and failure with a truthy non-void value

    ryanofsky commented at 8:37 pm on August 22, 2022:

    re: #25665 (review)

    I think it would be helpful to also test success with a falsy non-void value and failure with a truthy non-void value

    Makes sense, added tests

  65. in src/util/result.h:162 in 9bd10728ba outdated
    163+    template <typename Fn, typename... Args>
    164+    void Construct(const Fn& fn, Error error, Args&&... args)
    165+    {
    166+        this->AddError(std::move(error.message));
    167+        Construct([&](auto&&... x) {
    168+            if (!this->m_info) this->m_info = std::make_unique<detail::ErrorInfo<F>>();
    


    stickies-v commented at 3:50 pm on August 19, 2022:
    I think this line is redundant since we already call this->AddError() right above?

    ryanofsky commented at 8:39 pm on August 22, 2022:

    re: #25665 (review)

    I think this line is redundant since we already call this->AddError() right above?

    I think it was needed to avoid a segfault if the error message was empty, but in any case this line is gone now from implementing your earlier suggestion to dedup this.

  66. in src/util/result.h:185 in 9bd10728ba outdated
    198+    void MoveConstruct(Result<OT, OF>& other)
    199     {
    200-        assert(has_value());
    201-        return std::get<1>(m_variant);
    202+        *this << std::move(other);
    203+        if (other) this->MoveValue(other); else if (other.m_info) this->m_info->failure = std::move(other.m_info->failure);
    


    stickies-v commented at 3:54 pm on August 19, 2022:
    nit: I think this should be on a new line

    ryanofsky commented at 8:02 pm on August 23, 2022:

    re: #25665 (review)

    nit: I think this should be on a new line

    I dropped the else if here since it was redundant, but I think I do prefer the compact style with one line

  67. in src/util/result.h:205 in 9bd10728ba outdated
    221+    template <typename OT, typename OF>
    222+    Result(Result<OT, OF>&& other) { MoveConstruct(other); }
    223+    Result& operator=(Result&& other)
    224     {
    225-        return has_value() ? std::move(value()) : std::forward<U>(default_value);
    226+        if (*this) this->DestroyValue(); else this->m_info->failure.reset();
    


    stickies-v commented at 4:18 pm on August 19, 2022:

    nit: ternary looks more appropriate?

    0        *this ? this->DestroyValue() : this->m_info->failure.reset();
    

    ryanofsky commented at 8:03 pm on August 23, 2022:

    re: #25665 (review)

    nit: ternary looks more appropriate?

    I think I prefer if statement to ternary here, and I’m not used to seeing ternary statements rather than expressions in the codebase, but would be happy to change if there are other opinions.


    stickies-v commented at 7:53 pm on August 24, 2022:

    Hmm you’re right, we’re not using ternaries as statements it seems. I couldn’t find a single instance of a one-line if ...; else either though, and unfortunately the developer notes are rather ambiguous on the topic.

    In that case I’d prefer if and else on separate lines (personal preference and to not be the first in the repo) but no strong view so won’t comment on it further.

  68. in src/util/result.h:208 in 9bd10728ba outdated
    227+        MoveConstruct(other);
    228+        return *this;
    229+    }
    230+    ~Result() { if (*this) this->DestroyValue(); }
    231+
    232+    //! Operator moving warning and error messages from one result to another.
    


    stickies-v commented at 4:23 pm on August 19, 2022:

    nit:

    0    //! Operator moving warning and error messages from other Result to this.
    

    ryanofsky commented at 8:04 pm on August 23, 2022:

    re: #25665 (review)

    nit:

    Thanks, that’s clearer

  69. in src/util/result.h:216 in 9bd10728ba outdated
    235+    template <typename OT, typename OF>
    236+    Result<OT, OF>&& operator<<(Result<OT, OF>&& other)
    237+    {
    238+        if (other.m_info && !this->m_info) {
    239+            this->m_info.reset(new detail::ErrorInfo<F>{.errors = std::move(other.m_info->errors),
    240+                                                        .warnings = std::move(other.m_info->warnings)});
    


    stickies-v commented at 4:42 pm on August 19, 2022:
    Since ErrorInfo doesn’t have a move constructor, do the move semantics make sense here? (I’m still easily confused by move semantics, so I’m probably missing something - sorry)

    ryanofsky commented at 8:04 pm on August 23, 2022:

    re: #25665 (review)

    Since ErrorInfo doesn’t have a move constructor, do the move semantics make sense here? (I’m still easily confused by move semantics, so I’m probably missing something - sorry)

    ErrorInfo should have an implicit ErrorInfo(ErrorInfo&&) move constructor, but it actually isn’t relevant here, because the new ErrorInfo object that’s being allocated isn’t being constructed with that constructor. Instead the newly allocated ErrorInfo is aggregate initialized, and the two errors and warnings variables are individually move-constructed, rather than the whole ErrorInfo object being move constructed.

    If the question here is whether the std::move calls here have any effect, the answer is yes they do because errors and warnings variables are vectors, and vectors have move constructors.


    stickies-v commented at 6:58 pm on August 24, 2022:

    Thank you for the thoughtful response, that was very helpful. Your usage of move semantics here now makes sense to me.

    Now I’m just confused that the designated initialization compiles fine even though the spec says it’s since C++20 and I haven’t configured with --enable-c++20 (as does the CI I think)?


    ryanofsky commented at 7:03 pm on August 24, 2022:

    Now I’m just confused that the designated initialization compiles fine even though the spec says it’s since C++20 and I haven’t configured with --enable-c++20 (as does the CI I think)?

    Oh, we are just outside of the c++17 spec there. The build enables designated initializers as an extension since #24531

  70. in src/util/result.h:33 in 9bd10728ba outdated
    28+}
    29+
    30+//! Error information only allocated if there are errors or warnings.
    31+template <typename F>
    32+struct ErrorInfo {
    33+    std::optional<std::conditional_t<std::is_same<F, void>::value, std::monostate, F>> failure;
    


    hernanmarino commented at 4:58 pm on August 19, 2022:

    @ryanofsky the following change fixes the warnings for me, but i don’t know if this is the best way to deal with this

    0    std::optional<std::conditional_t<std::is_same<F, void>::value, std::monostate, F>> failure = std::nullopt;
    

    ryanofsky commented at 8:56 pm on August 22, 2022:

    re: #25665 (review)

    @ryanofsky the following change fixes the warnings for me, but i don’t know if this is the best way to deal with this

    Thanks! I think it might be sufficient to replace = std::nullopt with {}, so I will try that first.

  71. stickies-v commented at 5:05 pm on August 19, 2022: contributor

    Concept ACK 9bd10728bada8b04d86f5621ee127713f628a9ad

    I’m not getting any compilation warnings:

    0g++ --version
    1Apple clang version 13.1.6 (clang-1316.0.21.2.5)
    2Target: arm64-apple-darwin21.5.0
    

    It’s a beautiful implementation and I’ve learned a lot while reviewing this. That’s both a compliment and a warning that my review shouldn’t weigh heavily, even if I’m doing it as thoroughly as I can. My main concern is that for everyone not already intimately familiar with C++, I think this takes a long time to review thoroughly. The genericness made it difficult to reason about for me. I haven’t come up with a simpler alternative so I don’t think I’ll want that to stand in the way of anything, though. Just something to be mindful of.

  72. Rspigler referenced this in commit f2f8c5be49 on Aug 21, 2022
  73. in src/util/result.h:29 in 590bc615a3 outdated
    23+template <typename T>
    24+void MoveElements(T& src, T& dest)
    25+{
    26+    dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
    27+    src.clear();
    28+}
    


    ryanofsky commented at 8:37 pm on August 22, 2022:

    re: #25665 (review)

    Since we’re expecting T to be a container type, would this benefit from living in span.h for reusability? The generic implementation/naming isn’t really necessary in result.h, and people won’t come looking here when they need something to move elements between containers.

    It would be ok to move this somewhere, especially if something else was calling it. But I don’t think span.h would be the right place since this shouldn’t work on a span due to spans having fixed size. It also seems ok to me to keep this as a private helper function in a detail namespace as long as it is only called one place.


    stickies-v commented at 7:11 pm on August 24, 2022:

    Sorry for the ghost comment. I had realized after posting that this comment about moving it to span.h wasn’t really applicable so I removed it, but apparently not timely enough. I agree with you.

    The only further thought I had was that if there’s no need to be generic (yet - and maybe never) it might be worth not doing that to simplify things a bit where possible? E.g. the below is easier to quickly understand imo (and maybe compiles a bit faster?):

    0//! Helper to move warnings and errors from one ErrorInfo to another.
    1void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest)
    2{
    3    dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
    4    src.clear();
    5}
    

    No strong views either way though, so feel free to ignore. Just trying to minimize on what’s already a lot of genericness.


    ryanofsky commented at 3:13 pm on August 25, 2022:

    re: #25665 (review)

    Interesting! I do think the generic version is shorter and easier to understand. But I like moving more code from the .h file to the .cpp file, and I like the consistency between JoinMessages and MoveMessages, so I took this suggestion. Thanks!

  74. ryanofsky force-pushed on Aug 24, 2022
  75. ryanofsky commented at 6:30 pm on August 24, 2022: contributor

    Thanks for testing and reviews!

    Updated 9bd10728bada8b04d86f5621ee127713f628a9ad -> 10e158a5b57ba3a26e5046a9b42fcc757652f35a (pr/bresult2.13 -> pr/bresult2.14, compare) with suggestions

  76. ryanofsky force-pushed on Aug 25, 2022
  77. ryanofsky commented at 4:34 pm on August 25, 2022: contributor
    Updated 10e158a5b57ba3a26e5046a9b42fcc757652f35a -> 5aff7baf375c432746dff6862e9d06064ea1fb18 (pr/bresult2.14 -> pr/bresult2.15, compare) adding MoveMessages suggestion and few more comments and simplifications.
  78. in src/util/result.h:24 in 5aff7baf37 outdated
    19+namespace detail {
    20+//! Empty string list
    21+const std::vector<bilingual_str> EMPTY_LIST{};
    22 
    23+//! Helper function to join messages in space separated string.
    24+bilingual_str JoinMessages(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str> warnings);
    


    maflcko commented at 3:36 pm on August 29, 2022:
    In case you don’t remove this, this is wrong anyway, as it is missing a & in the second argument

    ryanofsky commented at 4:11 pm on August 30, 2022:

    re: #25665 (review)

    In case you don’t remove this, this is wrong anyway, as it is missing a & in the second argument

    Thanks, removed from the main commit and added&

  79. in src/util/result.h:95 in 5aff7baf37 outdated
    90+    //! Success check.
    91+    explicit operator bool() const { return !m_info || !m_info->failure; }
    92+
    93+    //! Error retrieval.
    94+    template <typename _F = F>
    95+    std::enable_if_t<!std::is_same<_F, void>::value, const _F&> GetFailure() const { assert(!*this); return *m_info->failure; }
    


    maflcko commented at 3:39 pm on August 29, 2022:

    nit: Can use is_same_v instead of ...::value? Also, missing LIFETIMEBOUND?

    (Same feedback on other lines where this is applicable)


    ryanofsky commented at 4:11 pm on August 30, 2022:

    re: #25665 (review)

    nit: Can use is_same_v instead of ...::value? Also, missing LIFETIMEBOUND?

    Thanks, added these improvements

  80. ryanofsky force-pushed on Aug 30, 2022
  81. ryanofsky commented at 6:27 pm on August 30, 2022: contributor
    Updated 5aff7baf375c432746dff6862e9d06064ea1fb18 -> 834857e56b8de0bfabee7315622c0211b4a48746 (pr/bresult2.15 -> pr/bresult2.16, compare) with suggestions, and splitting the main commit
  82. in src/util/result.h:85 in 834857e56b outdated
    16+#include <vector>
    17 
    18 namespace util {
    19+namespace detail {
    20+//! Empty string list
    21+const std::vector<bilingual_str> EMPTY_LIST{};
    


    stickies-v commented at 7:21 pm on August 30, 2022:
    nit: EMPTY_LIST ( and #include <vector> ) seems unnecessary in this commit, think it belongs in “refactor: Add util::Result multiple error and warning messages

    ryanofsky commented at 2:05 pm on September 1, 2022:

    re: #25665 (review)

    nit: EMPTY_LIST ( and #include <vector> ) seems unnecessary in this commit, think it belongs in “refactor: Add util::Result multiple error and warning messages

    Good catch! Removed

  83. in src/util/result.h:108 in 834857e56b outdated
    36+
    37+//! Result base class which is inherited by Result<T, F>.
    38+//! T is the type of the success return value, or void if there is none.
    39+//! F is the type of the failure return value, or void if there is none.
    40+template <typename T, typename F>
    41+class ResultBase;
    


    w0xlt commented at 10:41 pm on August 30, 2022:

    I think it’s a little confusing. If I understand correctly, template <typename F> class ResultBase<void, F> needs to know about template <typename T, typename F> class ResultBase; but the latter can only be declared after the former because it is a derived class. Therefore, a forward declaration is required.

    If the ResultBase classes shouldn’t be used anywhere outside of result.h and their only purpose is to be the base class of Result, I would suggest merging them all into a single class.


    ryanofsky commented at 1:57 am on August 31, 2022:

    Hmm, I wonder if you can say more about what is confusing or misleading. This is just a forward declaration for a template class.

    It is true that the reason for the forward declaring ResultBase<T, F> is to allow it to inherit from ResultBase<void, F>. So ResultBase<T, F> must be defined after ResultBase<void, F>, but declared before it.

    But this is a pretty standard thing for C and C++ code. Definition of one thing 1 depends on declaration of thing 2, so thing 2 needs to be forward declared. It can happen for normal classes and functions as well as templates.

    If the ResultBase classes shouldn’t be used anywhere outside of result.h and their only purpose is to be the base class of Result, I would suggest merging them all into a single class.

    This isn’t easily possible because Result<void, F> inherits directly from detail::ResultBase<void, F> and does not inherit from detail::ResultBase<T, F>. It does not have an m_value member or value() functions or a dereferencing operator. If the result type T is void the Result class doesn’t hold a value and can’t be dereferenced.

  84. w0xlt approved
  85. in src/util/result.h:351 in 834857e56b outdated
    262+//! intended for simple applications where there's probably only one error or
    263+//! warning message to report, but multiple messages should not be lost if they
    264+//! are present. More complicated applications should use GetErrors() and
    265+//! GetWarning() methods directly.
    266+template <typename T, typename F>
    267+bilingual_str ErrorString(const Result<T, F>& result) { return detail::JoinMessages(result.GetErrors(), result.GetWarnings()); }
    


    stickies-v commented at 10:50 am on August 31, 2022:

    Since the friend declaration is gone, I think this should be inline?

    0inline bilingual_str ErrorString(const Result<T, F>& result) { return detail::JoinMessages(result.GetErrors(), result.GetWarnings()); }
    

    ryanofsky commented at 2:06 pm on September 1, 2022:

    re: #25665 (review)

    Since the friend declaration is gone, I think this should be inline?

    Yes, makes sense to be inline.

  86. stickies-v commented at 11:33 am on August 31, 2022: contributor
    Code Review ACK 834857e56
  87. ryanofsky force-pushed on Sep 1, 2022
  88. ryanofsky commented at 3:21 pm on September 1, 2022: contributor

    Thanks for the reviews! Just tweaked a few things as suggested.

    Updated 834857e56b8de0bfabee7315622c0211b4a48746 -> 82c549aa538a5318fdb56d91117b4c9fc43737de (pr/bresult2.16 -> pr/bresult2.17, compare) with suggestions

  89. ryanofsky force-pushed on Sep 1, 2022
  90. ryanofsky commented at 9:02 pm on September 1, 2022: contributor

    Some changes made earlier in #25665#pullrequestreview-1085638384 broke derived-to-base type conversions used in followup PR #25722. Latest push fixes this and adds a test.

    Updated 82c549aa538a5318fdb56d91117b4c9fc43737de -> c14e904f66505b3e89ca1138c8d2fa4e3d0916d0 (pr/bresult2.17 -> pr/bresult2.18, compare) adding fix and test for derived to base conversions

  91. in src/util/result.h:57 in c14e904f66 outdated
    52+    {
    53+        if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
    54+        return *m_info;
    55+    }
    56+
    57+    //! Value accessors that do nothing this because class has value type T=void.
    


    stickies-v commented at 3:42 pm on September 7, 2022:

    nit: typo

    0    //! Value accessors that do nothing because this class has value type T=void.
    

    ryanofsky commented at 6:59 pm on September 12, 2022:

    re: #25665 (review)

    nit: typo

    Thanks, fixed

  92. stickies-v commented at 4:40 pm on September 7, 2022: contributor
    Code review re-ACK c14e904f66505b3e89ca1138c8d2fa4e3d0916d0
  93. in src/util/result.h:53 in 3507da864a outdated
    48+    //! Success check.
    49+    explicit operator bool() const { return !m_info; }
    50+
    51+    //! Error retrieval.
    52+    template <typename _F = F>
    53+    std::enable_if_t<!std::is_same_v<_F, void>, const _F&> GetFailure() const LIFETIMEBOUND { assert(!*this); return m_info->failure; }
    


    maflcko commented at 12:51 pm on September 12, 2022:

    nit in https://github.com/bitcoin/bitcoin/commit/3507da864a1dd7be1bc72ada26d830a4da0c37ae:

    I think in C++17 you can remove all of the template and enable_if_t stuff and just write const auto& GetFailure() const for the return type.


    ryanofsky commented at 7:01 pm on September 12, 2022:

    re: #25665 (review)

    I think in C++17 you can remove all of the template and enable_if_t stuff and just write const auto& GetFailure() const for the return type.

    Thanks, that is better. Simplified now.


    maflcko commented at 5:33 pm on September 13, 2022:
    Hmm, I meant const auto&, not auto, or is auto in this context magically the same as const auto&?

    ryanofsky commented at 6:56 pm on September 13, 2022:

    re: #25665 (review)

    Hmm, I meant const auto&, not auto, or is auto in this context magically the same as const auto&?

    No, I just messed up and unintentionally did a copy. Fixed this and added tests to make sure GetFailure() does not copy.

  94. in src/util/result.h:241 in c14e904f66 outdated
    245+//! intended for simple applications where there's probably only one error or
    246+//! warning message to report, but multiple messages should not be lost if they
    247+//! are present. More complicated applications should use GetErrors() and
    248+//! GetWarning() methods directly.
    249+template <typename T, typename F>
    250+inline bilingual_str ErrorString(const Result<T, F>& result) { return detail::JoinMessages(result.GetErrors(), result.GetWarnings()); }
    


    maflcko commented at 1:38 pm on September 12, 2022:

    nit in c14e904f66505b3e89ca1138c8d2fa4e3d0916d0:

    Can remove inline, as all template are inline by definition.


    ryanofsky commented at 7:02 pm on September 12, 2022:

    re: #25665 (review)

    Can remove inline, as all template are inline by definition.

    Thanks, removed inline

  95. in src/util/result.h:72 in 3507da864a outdated
    67+    template <typename O>
    68+    void MoveValue(O& other) { new (&m_value) T{std::move(other.m_value)}; }
    69+    void DestroyValue() { m_value.~T(); }
    70+
    71+    ResultBase() {}
    72+    ~ResultBase() {}
    


    maflcko commented at 2:14 pm on September 12, 2022:

    nit in 3507da864a1dd7be1bc72ada26d830a4da0c37ae:

    Maybe add a comment to explain those a bit more? While the ResultBase() constructor leaves the object uninitialized, the Result constructor guarantees that the object is either filled with a value or an error.


    ryanofsky commented at 7:03 pm on September 12, 2022:

    re: #25665 (review)

    Maybe add a comment to explain those a bit more? While the ResultBase() constructor leaves the object uninitialized, the Result constructor guarantees that the object is either filled with a value or an error.

    Sure, I moved the DestroyValue call to this ~ResultBase destructor instead of the other Result constructor to make this more self contained and clear. Also added a comment explaining why the empty constructor is required. Hopefully these are improvements. Also happy to make other changes to clarify.

  96. in src/util/result.h:153 in 3507da864a outdated
    165+
    166+    template <typename, typename>
    167+    friend class Result;
    168+
    169+public:
    170+    //! Constructors, destructor, and assignment operator.
    


    maflcko commented at 2:18 pm on September 12, 2022:

    nit in 3507da864a1dd7be1bc72ada26d830a4da0c37ae:

    doxygen will attach the comment to the constructor. In any case, I think this can be removed since it is not adding any info that isn’t already there.


    ryanofsky commented at 7:04 pm on September 12, 2022:

    re: #25665 (review)

    nit in 3507da8:

    doxygen will attach the comment to the constructor. In any case, I think this can be removed since it is not adding any info that isn’t already there.

    Thanks, dropped. This was used to group methods in an earlier version of PR that had more methods below.

  97. in src/util/result.h:41 in 3507da864a outdated
    36+class ResultBase<void, F>
    37+{
    38+protected:
    39+    std::unique_ptr<ErrorInfo<F>> m_info;
    40 
    41+    //! Value accessors that do nothing this because class has value type T=void.
    


    maflcko commented at 2:23 pm on September 12, 2022:

    nit in 3507da864a1dd7be1bc72ada26d830a4da0c37ae:

    Are those really “accessors”, not “setters”?


    ryanofsky commented at 7:05 pm on September 12, 2022:

    re: #25665 (review)

    nit in 3507da8:

    Are those really “accessors”, not “setters”?

    Changed to setters (I thought accessors was a general term for getters and setters)

  98. in src/util/result.h:15 in 3507da864a outdated
     7@@ -8,16 +8,102 @@
     8 #include <attributes.h>
     9 #include <util/translation.h>
    10 
    11+#include <memory>
    12+#include <optional>
    13+#include <tuple>
    14+#include <utility>
    15 #include <variant>
    


    maflcko commented at 2:29 pm on September 12, 2022:

    nit in 3507da864a1dd7be1bc72ada26d830a4da0c37ae:

    Seems overkill to include variant, when it could be replaced by copy-pasting the one line:

    0struct MonoState{}; // Similar to std::monostate
    

    ryanofsky commented at 7:05 pm on September 12, 2022:

    re: #25665 (review)

    Seems overkill to include variant, when it could be replaced by copy-pasting the one line:

    Thanks, dropped the dependency on variant

  99. maflcko commented at 3:36 pm on September 12, 2022: member

    review ACK f7b4fa870783ecd5f9a408bd603ff9cf0399cc3e 🛍

    Signature:

     0-----BEGIN PGP SIGNED MESSAGE-----
     1Hash: SHA512
     2
     3review ACK f7b4fa870783ecd5f9a408bd603ff9cf0399cc3e 🛍
     4-----BEGIN PGP SIGNATURE-----
     5
     6iQGyBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
     7pUjixAv439pj0FjYrwKsRqf1jJfBZhalBe6IAeD8yperLaCmdGfmSp0udgbTwuGI
     85YMasCELJ3PikDrscmn6snYiYBb+0PMgPMqriUKMWxmmMDW5v9aiqefxSgEGBQro
     92euNt+qSvycWOHt2We2oZiRyqGZZooQNMBOETo4y6v164pdysueFKNFac3ppI3AW
    10LLepp/FEaId5t8dIlaVo7q49I55AQ0xdduptslA2GmZCcXyVA7bNZrcQxc2NxoH7
    11XZFFTbkYri51Nn8bRcD/EK47Cc04CCqw8p9XVeD4xpkZBzqoHKIk/3wlaqN2qgJb
    128cgK6xoBOQIsKk43ez3Kxhip6pjQ3fAVwqThz9S9Csf7Td1qCmrhM59n/2eMOz5Y
    1330XwsiGigRr9IDr5Hi3XxrRZzyUwmqZyvN+n3JvT7lkzQhCDPkIozJPGFWgQEfRC
    14XuJQdRQW7LOZH1GVnXRP1/wCSNpcXwjqNDT+syYEQk91/qezM+NJ1yfToQJkXC7i
    15eIhpCy8=
    16=b8pq
    17-----END PGP SIGNATURE-----
    
  100. ryanofsky force-pushed on Sep 13, 2022
  101. ryanofsky force-pushed on Sep 13, 2022
  102. ryanofsky commented at 3:22 pm on September 13, 2022: contributor

    Thanks for the reviews! New pushes implement all the suggested changes.

    Updated c14e904f66505b3e89ca1138c8d2fa4e3d0916d0 -> 05a97d3208cc365cdeac9de281529568b3cd056c (pr/bresult2.18 -> pr/bresult2.19, compare) with suggestions. Also replaced operator« with operator» to simplify followup PR #25722 a bit Rebased 05a97d3208cc365cdeac9de281529568b3cd056c -> e04d8a754ff1b25cab483996319a583e6e3e680a (pr/bresult2.19 -> pr/bresult2.20, compare) due to conflict with #24513

  103. maflcko commented at 5:36 pm on September 13, 2022: member

    Still need to review the last commit

    review ACK 3af5f5adbb 🍒

    Signature:

     0-----BEGIN PGP SIGNED MESSAGE-----
     1Hash: SHA512
     2
     3review ACK 3af5f5adbb 🍒
     4-----BEGIN PGP SIGNATURE-----
     5
     6iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
     7pUg+kgv/VMHBkcM/thQ5I6k4qE4vJdWFYuZQJFTe/KVPIpkC6nxuEr6Gi3staL6n
     8mdRzkHsqS1Wpsm/kMUREBvEuTqUk+SPyv+spyWhnd86VSTa66clfJvrp0AD2kV6w
     9uX5DlPzhb5fvgK59gJZ59Yq8bIcwqLe+hzvRBKdlqh5qxqnm1zSAB0O+KK4pwk9N
    10z0mlPBWnM8qgmB3nnS9tWN58E4PUsue1Xb89GgNHh4DrnZTo/UrAPC9qsABeSU7+
    11u1pU1Z9rpn57qmQI1LkQ9FDjZE8mL5NZ12OpISsNiXYVzQey6IiKsxzjfVtg3R4q
    125qtHzHtzx7XjDT40oAeb1Z1EWm+an5KME7Gk4/olR0PqDo2f2LILuwp9kCJJY7qt
    13IdRWvHwAmU5J9EvgPOTWzWtU+LbsVwS3qoBwee2tlWrAxBjkdezuESFCDepq42wL
    14TqzGBWAL40+MdjLBSeOYJm0ZuZHSHmqIDfykVyDmdM9SBaPstfkRmBngRIEKoCpM
    159PGi4h4Y
    16=W7bY
    17-----END PGP SIGNATURE-----
    
  104. ryanofsky force-pushed on Sep 13, 2022
  105. ryanofsky commented at 7:17 pm on September 13, 2022: contributor
    Updated e04d8a754ff1b25cab483996319a583e6e3e680a -> 52a4e50fb4b1171ee0f6814b0a50bc70cdd77134 (pr/bresult2.20 -> pr/bresult2.21, compare) fixing unintentional GetFailure copy introduced last push, and adding test to detect this copy
  106. in src/util/result.h:271 in 52a4e50fb4 outdated
    219+        if (!warning.empty()) this->Info().warnings.emplace_back(std::move(warning));
    220+    }
    221+
    222+    //! Operator moving warning and error messages from this result object to
    223+    //! another one. Only moves message strings, does not change success or
    224+    //! failure values of either Result object.
    


    maflcko commented at 9:34 am on September 14, 2022:

    52a4e50fb4b1171ee0f6814b0a50bc70cdd77134: I don’t like that this takes over error strings, but leaves the value and failure untouched. It seems fine to have a result and warnings, but having a result and also an error at the same time seems odd.

    Same with operator=. I think this is the first time I’ve seen that after calling =, state is preserved from before = was called.


    ryanofsky commented at 8:37 pm on September 14, 2022:

    re: #25665 (review)

    52a4e50: I don’t like that this takes over error strings, but leaves the value and failure untouched. It seems fine to have a result and warnings, but having a result and also an error at the same time seems odd.

    Agree having both a result value and an error message should be avoided. Also having neither a result value nor an error message should be avoided. But there are tradeoffs around where and how strictly you want to enforce these things. The main thing currently enforcing value/error consistency is having a constructor that sets error messages and failure values at the same time and does not allow setting a result value, and a constructor that only sets a success value and does not allow setting an error message.

    But this leaves open the question of what helpers functions like operator>> should do when they combine multiple result values that have already been constructed.

    The use-case for operator>> is when you have an outer function returning Result<T, F> calling inner functions returning Result<T1, F1>, and Result<T2, F2>, etc. Examples would be LoadWalletInternal, CreateWallet, and DoMigration from #25722. The outer function can handle failures from inner functions it calls any way it wants: passing failure values up to its caller, translating failure values, ignoring failures, retrying after failures, falling back to an alternate approaches, etc. It sets success and failure values directly, and it can use operator>> to collect error and warning messages separately and pass them along. I don’t think operator>> should be involved in success and failure value handling. I think it would be bad if operator>> discarded error messages, or it it threw runtime exceptions, instead of just passing messages on to ultimately get displayed or logged.

    In C++ generally operator>> is used for things as varied as bit shifting and stream I/O and can be interpreted as “move data from this place to another place” so I think it reasonable that this operator>> just moves error and warning messages from one Result object to another, as long as behavior is clearly documented.

    Same with operator=. I think this is the first time I’ve seen that after calling =, state is preserved from before = was called.

    Yes it is true that assigning to an existing result does not erase warning and errors messages already accumulated in the result. It only sets the success or failure value and appends new warning and error messages to existing ones.

    I think this this behavior is safe and useful. Setting a value should not automatically erase warning and error messages that are meant to be displayed or logged. But if this behavior is too surprising for an operator= method, we don’t actually need to make Result assignable, and could rename operator=() to Set() or SetValue(). It looks like even after #25722 there are only 3 operator= calls in the codebase outside of tests, so this would be an easy change.


    maflcko commented at 7:53 am on September 15, 2022:

    Also having neither a result value nor an error message should be avoided.

    Well, this is already impossible for the reasons you gave. (Edit: When calling only the constructors)

    Agree having both a result value and an error message should be avoided.

    Then, why not make it impossible? If there is an outer function returning Result<T, F> that wants to combine Result<T1, F1> and Result<T2, F2>, then it seems better if it explicitly takes (moves) the result T1/F1 out and translates it into T/F. For example, if it passes up a failure value, it seems best to just create a fresh Result (of the outer type) with the failure and return that. (Same if it translates failure values). If it ignores failures, it would be good to translate them to warnings first and not blindly take them over as errors with the >> operator. (Same if it retries or falls back).

    I think this this behavior is safe and useful.

    Why couldn’t the same be achieved by explicitly constructing a new Result with either an error or a value?


    ryanofsky commented at 9:24 am on September 15, 2022:

    Also having neither a result value nor an error message should be avoided.

    Well, this is already impossible for the reasons you gave.

    “Should be avoided” means that users should avoid it, and the implementation makes it easy to avoid by default. The reasons I gave were reasons why a function combining multiple result values needs to either (1) allow result values and error messages to exist at the same time or (2) discard error messages or result values or (3) throw exceptions. And I believe the best choice for operator>> is (1), just to be a plain message mover that moves message strings and leaves result values alone. I linked to use-cases showing how this works in practice.

    Agree having both a result value and an error message should be avoided.

    Then, why not make it impossible? If there is an outer function returning Result<T, F> that wants to combine Result<T1, F1> and Result<T2, F2>, then it seems better if it explicitly takes (moves) the result T1/F1 out and translates it into T/F. For example, if it passes up a failure value, it seems best to just create a fresh Result (of the outer type) with the failure and return that.

    This is actually what the implementation does. But if the outer function returns success value, and there are error messages, I don’t think it is good default behavior to throw away the error messages or raise an exception. I think the best default behavior is to keep error messages so they can be displayed or logged.

    (Same if it translates failure values). If it ignores failures, it would be good to translate them to warnings first and not blindly take them over as errors with the >> operator. (Same if it retries or falls back).

    So change you are asking for there is basically:

     0--- a/src/util/result.h
     1+++ b/src/util/result.h
     2@@ -222,7 +222,7 @@ public:
     3     Result&& operator>>(O&& other LIFETIMEBOUND) &&
     4     {
     5         if (this->m_info) {
     6-            if (!this->m_info->errors.empty()) detail::MoveMessages(this->m_info->errors, other.Info().errors);
     7+            if (!this->m_info->errors.empty()) detail::MoveMessages(this->m_info->errors, other ? other.Info().warnings : other.Info().errors);
     8             if (!this->m_info->warnings.empty()) detail::MoveMessages(this->m_info->warnings, other.Info().warnings);
     9         }
    10         return std::move(*this);
    

    I wouldn’t object to it, but it just seems more invasive and doesn’t offer practical benefits.

    I think this this behavior is safe and useful.

    Why couldn’t the same be achieved by explicitly constructing a new Result with either an error or a value?

    I think

    0WarnFn1() >> result;
    1WarnFn2() >> result;
    2result = FailFn(...);
    3return result;
    

    or

    0WarnFn1() >> result;
    1WarnFn2() >> result;
    2result.Set(FailFn(...));
    3return result;
    

    is cleaner than

    0WarnFn1() >> result;
    1WarnFn2() >> result;
    2auto result2 = FailFn(...);
    3std::move(result) >> result2;
    4return result2;
    

    because it doesn’t require introducing multiple result variables. If you are trying to get rid of both operator= and operator>>, I believe operator= or Set is also better than:

    0auto result2 = FailFn(...);
    1return result2 ? Result<int, FnError>(std::move(result), result2.value()) : Result<int, FnError>(std::move(result), Error{}, resul2t.GetFailure());
    

    I’m happy to rename operator= to Set if you think operator= is misleading. But if you look at the places where these functions are used, it is easier to see why they are useful. Conversely, if you think there is a footgun here, it would be helpful to see an example of the footgun.


    ryanofsky commented at 9:54 am on September 15, 2022:

    But if you look at the places where these functions are used, it is easier to see why they are useful.

    I linked to some places where operator>> is used already: LoadWalletInternal, CreateWallet, and DoMigration from #25722

    For operator= (again happy to rename this to Set) examples are: AddressTableModel::addRow, SQLiteDatabase::Verify, AvailableCoinsTestingSetup, FuzzedWallet::GetScriptPubKey


    ryanofsky commented at 3:30 pm on September 15, 2022:
    Went ahead and renamed operator= to Set for now. Seems like a good way to avoid some confusion.
  107. ryanofsky force-pushed on Sep 15, 2022
  108. ryanofsky commented at 3:31 pm on September 15, 2022: contributor
    Updated 52a4e50fb4b1171ee0f6814b0a50bc70cdd77134 -> f9accbc6e296adadac374eca085f8b2ce095c8a4 (pr/bresult2.21 -> pr/bresult2.22, compare) just renaming operator= to Set to avoid some confusion Updated f9accbc6e296adadac374eca085f8b2ce095c8a4 -> 776d9b3fbb4cf83c81cc38c44cae10d3f3344b1b (pr/bresult2.22 -> pr/bresult2.23, compare) tweaking commit message Rebased 776d9b3fbb4cf83c81cc38c44cae10d3f3344b1b -> 456e3d4eccf010eba30096061b83adc45c371b92 (pr/bresult2.23 -> pr/bresult2.24, compare) due to conflict with #25499 Rebased 456e3d4eccf010eba30096061b83adc45c371b92 -> 28a6934da980703006e028776d276ae77121c586 (pr/bresult2.24 -> pr/bresult2.25, compare) due to conflict with #25667 Rebased 28a6934da980703006e028776d276ae77121c586 -> f4d55d858d9da08612a8ba3b7ceeaf36dfe6cc30 (pr/bresult2.25 -> pr/bresult2.26, compare) due to conflicts with #26289 and #26661
  109. ryanofsky force-pushed on Sep 15, 2022
  110. DrahtBot added the label Needs rebase on Sep 16, 2022
  111. ryanofsky force-pushed on Sep 20, 2022
  112. DrahtBot removed the label Needs rebase on Sep 20, 2022
  113. DrahtBot added the label Needs rebase on Oct 13, 2022
  114. ryanofsky force-pushed on Oct 14, 2022
  115. DrahtBot removed the label Needs rebase on Oct 14, 2022
  116. DrahtBot added the label Needs rebase on Jan 3, 2023
  117. ryanofsky force-pushed on Jan 6, 2023
  118. DrahtBot removed the label Needs rebase on Jan 6, 2023
  119. janus referenced this in commit 56ca68bb23 on Jan 20, 2023
  120. DrahtBot added the label Needs rebase on Jan 27, 2023
  121. ryanofsky force-pushed on Feb 10, 2023
  122. ryanofsky force-pushed on Feb 10, 2023
  123. DrahtBot removed the label Needs rebase on Feb 10, 2023
  124. hebasto commented at 2:54 pm on February 12, 2023: member
    Concept ACK.
  125. hebasto commented at 3:27 pm on February 12, 2023: member
    It seems the 7cdb7d1e9573ae60e7335af5d3de99191ad68b3f commit adds src/wallet/test/availablecoins_tests.cpp by accident, doesn’t it?
  126. hebasto commented at 5:42 pm on February 12, 2023: member

    Approach ACK eb50fcd6859d1730663159995e8477f6d892e7f4.


    Style nit:

    https://github.com/bitcoin/bitcoin/pull/25665/files#r954244319:

    In that case I’d prefer if and else on separate lines

    Agree. From Developer Notes:

    In every other case, braces are required, and the then and else clauses must appear correctly indented on a new line.

  127. in src/util/result.h:159 in eb50fcd685 outdated
    145+//!
    146+//! Most code does not need different error-handling behavior for different
    147+//! types of errors, and can suffice just using the type `T` success value on
    148+//! success, and descriptive error strings when there's a failure. But
    149+//! applications that do need more complicated error-handling behavior can
    150+//! override the default `F = void` failure type and get failure values by
    


    maflcko commented at 10:42 am on February 13, 2023:

    Allowing for a void failure type seems to make this incompatible to be switched out to std::expected https://en.cppreference.com/w/cpp/utility/expected ?

    With multiple warning and error messages this may already be incompatible, though?


    ryanofsky commented at 3:27 pm on February 13, 2023:

    re: #25665 (review)

    Allowing for a void failure type seems to make this incompatible to be switched out to std::expected https://en.cppreference.com/w/cpp/utility/expected ?

    With multiple warning and error messages this may already be incompatible, though?

    Yes, if you are thinking that util::Result could wrap std::expected, that would be probably be awkward and not worth it.

    But I don’t think there is a conflict because the two classes are mostly doing different things. The util::Result class is mostly providing error-reporting functionality (passing error and warning strings up to the user). The std::expected class is only providing error-handling functionality (passing failure and success values between functions). Here is how I would choose the between the two classes:

    • If your function never fails, it should just return success value directly.
    • If your function can fail, but doesn’t provide any error strings or specific failure information, it should return std::optional.
    • If your function can fail, and provides failure information but not error strings, it should return std::expected.
    • If your function can fail, and generates error or warning strings it should return util::Result.

    We have a lot of functions that generate error strings as you can see by all the code using util::Error and util::Result presently, and in more code that is converted to use util::Result in this PR and #25722. After std::expected is available, most of these functions still be better off using util::Result instead of std::expected so they are able to pass back error strings in a uniform way.

    But when std::expected is available, we may want to tweak the util::Result class to make it easier to switch between std::expected and util::Result with minimal code changes. For example, the util::Result already has a value_or method to be compatible with std::optional. It could also have and_then and or_else methods to be compatible with std::expected.

  128. ryanofsky force-pushed on Feb 16, 2023
  129. ryanofsky commented at 6:54 pm on February 16, 2023: contributor

    re: #25665#pullrequestreview-1294756069

    Thanks for the review! I got rid of the unused test file and changed the if formatting as suggested


    Updated eb50fcd6859d1730663159995e8477f6d892e7f4 -> 501ef88b9412b0d924abf32cf2de7fbcbbb69b8d (pr/bresult2.28 -> pr/bresult2.29, compare) with suggested changes

  130. DrahtBot added the label Needs rebase on Feb 22, 2023
  131. ryanofsky force-pushed on Mar 1, 2023
  132. DrahtBot removed the label Needs rebase on Mar 1, 2023
  133. DrahtBot added the label Needs rebase on Mar 8, 2023
  134. ryanofsky force-pushed on Mar 16, 2023
  135. DrahtBot removed the label Needs rebase on Mar 16, 2023
  136. DrahtBot added the label Needs rebase on Mar 23, 2023
  137. ryanofsky force-pushed on Apr 4, 2023
  138. DrahtBot removed the label Needs rebase on Apr 4, 2023
  139. DrahtBot added the label CI failed on Apr 24, 2023
  140. ryanofsky force-pushed on May 2, 2023
  141. DrahtBot removed the label CI failed on May 2, 2023
  142. hernanmarino approved
  143. hernanmarino commented at 4:42 pm on May 11, 2023: contributor
    re ACK 28a954c7034077ac3a45083dd5e2b5cdb4d4cdde
  144. DrahtBot requested review from maflcko on May 11, 2023
  145. DrahtBot requested review from stickies-v on May 11, 2023
  146. DrahtBot requested review from w0xlt on May 11, 2023
  147. ryanofsky commented at 5:30 pm on May 11, 2023: contributor

    Thanks for the review.

    Note: @martinus left several review comments on #25722#pullrequestreview-1386736519, which is based on this PR, which apply to this PR and can improve it a little. I’m planning to update this PR to incorporate the suggestions.

  148. DrahtBot removed review request from w0xlt on May 11, 2023
  149. DrahtBot requested review from w0xlt on May 11, 2023
  150. DrahtBot added the label Needs rebase on May 26, 2023
  151. maflcko commented at 1:35 pm on May 26, 2023: member
    void can be removed from OP?
  152. DrahtBot removed review request from w0xlt on May 26, 2023
  153. DrahtBot requested review from w0xlt on May 26, 2023
  154. in src/test/result_tests.cpp:159 in 28a954c703 outdated
    136+util::Result<int, int> TruthyFalsyFn(int i, bool success)
    137+{
    138+    if (success) return i;
    139+    return {util::Error{Untranslated(strprintf("failure value %i.", i))}, i};
    140+}
    141+
    


    TheCharlatan commented at 4:28 pm on June 19, 2023:

    Can you add an example for when the Result error type in a chain of function calls remains the same, but the value type changes? Is there a better way to do this conversion than manually constructing the result again (like below)?

    0util::Result<std::string, FnError> CastFailFn() {
    1    auto res = IntFailFn(1, false);
    2    return {util::Error{ErrorString(res)}, res.GetFailure()};
    3}
    

    ryanofsky commented at 4:25 pm on July 21, 2023:

    re: #25665 (review)

    Can you add an example for when the Result error type in a chain of function calls remains the same, but the value type changes? Is there a better way to do this conversion than manually constructing the result again (like below)?

    Yes, there is definitely a better way to pass along errors and warnings from one result to another without calling ErrorString and flattening them. The util::Result constructor will move errors and warnings directly from an existing result object if you just pass the other result object as an argument with std::move. If you look at the MultiWarnFn there was an example of this feature in the success case. But I added a new StrFailFn function now based on your code that demonstrates it in both the success and failure cases.

    I am also thinking of adding a util::Messages{Result&&} helper similar to existing util::Error{std::string} and util::Warning{std::string} helpers to make it more obvious how you can construct a Result value with errors and warning from different sources.

    Revisiting this makes me realize I should probably add a tutorial-style document that describes how to use the Result class with standalone functions that return util::Result, chained functions that return util::Result values of the same type, and chained functions that return util::Result values of different types.


    jonatack commented at 7:32 pm on July 23, 2023:

    I should probably add a tutorial-style document that describes how to use the Result class with standalone functions that return util::Result, chained functions that return util::Result values of the same type, and chained functions that return util::Result values of different types.

    I think this would be valuable, either in util/result.h directly or in doc/developer-notes.md or another file in /doc.


    TheCharlatan commented at 2:33 pm on July 27, 2023:

    I am also thinking of adding a util::Messages{Result&&} helper.

    That sounds like a worthwhile improvement of the ergonomics here.


    ryanofsky commented at 5:45 pm on August 1, 2023:

    re: #25665 (review)

    I am also thinking of adding a util::Messages{Result&&} helper.

    That sounds like a worthwhile improvement of the ergonomics here.

    Thanks, added this

  155. DrahtBot removed review request from w0xlt on Jun 19, 2023
  156. DrahtBot requested review from w0xlt on Jun 19, 2023
  157. ryanofsky force-pushed on Jul 21, 2023
  158. ryanofsky commented at 4:47 pm on July 21, 2023: contributor

    Rebased 28a954c7034077ac3a45083dd5e2b5cdb4d4cdde -> 40f09de73e61e7ae62d6639a49b7c7ac48d514d9 (pr/bresult2.33 -> pr/bresult2.34, compare) due to conflict with various PR and making many suggested changes from review #25722 (which is based on this PR)

    re: #25665 (comment)

    void can be removed from OP?

    Thanks, no longer mentioning it since #25977 added support for Result<void>

  159. DrahtBot removed the label Needs rebase on Jul 21, 2023
  160. DrahtBot added the label CI failed on Jul 21, 2023
  161. in src/util/result.h:47 in f1b46f4017 outdated
    38@@ -39,13 +39,23 @@ class Result
    39 
    40     std::variant<bilingual_str, T> m_variant;
    41 
    42+    //! Make operator= private and instead require explicit Set() calls to
    43+    //! avoid confusion in the future when the Result class gains support for
    44+    //! richer errors and callers want to set result values without erasing
    45+    //! error strings.
    46+    Result& operator=(const Result&) = default;
    47+    Result& operator=(Result&&) = default;
    


    jonatack commented at 7:02 pm on July 21, 2023:

    f1b46f4017a

    0init.cpp:948:12: error: 'operator=' is a private member of 'util::Result<void>'
    1    result = init::SetLoggingLevel(args);
    2    ~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    3./util/result.h:47:13: note: declared private here
    4    Result& operator=(Result&&) = default;
    5            ^
    

    ryanofsky commented at 7:52 pm on July 21, 2023:

    re: #25665 (review)

    Thanks for testing the early commit, should be fixed now

  162. in src/test/result_tests.cpp:101 in 40f09de73e outdated
     98+
     99+util::Result<std::string, FnError> StrFailFn(int i, bool success)
    100+{
    101+    auto result = IntFailFn(i, success);
    102+    if (!success) return {std::move(result), util::Error{Untranslated("str error")}, result.GetFailure()};
    103+    return {std::move(result), std::to_string(*result)};
    


    jonatack commented at 7:10 pm on July 21, 2023:

    40f09de73e6 lint-locale-dependence.py

    0The locale dependent function std::to_string(...) appears to be used:
    1src/test/result_tests.cpp:101:    return {std::move(result), std::to_string(*result)};
    

    ryanofsky commented at 7:52 pm on July 21, 2023:

    re: #25665 (review)

    Thanks, this should be fixed now

  163. ryanofsky force-pushed on Jul 21, 2023
  164. ryanofsky commented at 8:04 pm on July 21, 2023: contributor
    Updated 40f09de73e61e7ae62d6639a49b7c7ac48d514d9 -> 775b54e88107b0b976bf995e607926013fa9bc42 (pr/bresult2.34 -> pr/bresult2.35, compare) with compile/lint fixes
  165. in src/util/result.h:17 in 775b54e881 outdated
     7@@ -8,16 +8,125 @@
     8 #include <attributes.h>
     9 #include <util/translation.h>
    10 
    11-#include <variant>
    12+#include <memory>
    13+#include <optional>
    14+#include <tuple>
    15+#include <utility>
    16+#include <vector>
    


    jonatack commented at 8:43 pm on July 21, 2023:

    332e847c9ec tuple not needed per iwyu in tidy ci https://cirrus-ci.com/task/6540065057275904?logs=ci#L20325, while touching could add the others

    0+#include <assert.h>
    1 #include <memory>
    2 #include <optional>
    3-#include <tuple>
    4 #include <utility>
    5 #include <vector>
    6+#include <new>
    7+#include <type_traits>
    

    ryanofsky commented at 5:45 pm on August 1, 2023:

    re: #25665 (review)

    332e847 tuple not needed per iwyu in tidy ci https://cirrus-ci.com/task/6540065057275904?logs=ci#L20325, while touching could add the others

    Thanks, updated includes

  166. in src/util/result.cpp:6 in 775b54e881 outdated
    0@@ -0,0 +1,28 @@
    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>
    


    jonatack commented at 8:45 pm on July 21, 2023:

    https://github.com/bitcoin/bitcoin/commit/332e847c9ec0241efd9681eee3b03ff819aaddc3 per iwyu in https://cirrus-ci.com/task/6540065057275904?logs=ci#L20305

    0 #include <util/result.h>
    1-#include <util/string.h>
    2+#include "util/translation.h"
    3+
    4+#include <algorithm>
    5+#include <initializer_list>
    6+#include <iterator>
    

    ryanofsky commented at 5:46 pm on August 1, 2023:
  167. jonatack commented at 9:20 pm on July 21, 2023: contributor

    First-pass ACK 775b54e88107b0b976bf995e607926013fa9bc42

    In https://github.com/bitcoin/bitcoin/commit/332e847c9ec0241efd9681eee3b03ff819aaddc3 and 4d995d3fa66fbc3eb87c6627e5ba1b2a809402a4, I wonder if some of the custom operator (i.e. move) definitions should have a noexcept-specification. Also, notating any methods where it would be incorrect if the return value isn’t checked (e.g. for error-handling) and optionally getter-like pure functions with nodiscard may aid reviewers / readers of the code.

  168. DrahtBot removed review request from w0xlt on Jul 21, 2023
  169. DrahtBot requested review from hernanmarino on Jul 21, 2023
  170. DrahtBot requested review from w0xlt on Jul 21, 2023
  171. DrahtBot removed review request from hernanmarino on Jul 21, 2023
  172. DrahtBot removed review request from w0xlt on Jul 21, 2023
  173. DrahtBot requested review from hernanmarino on Jul 21, 2023
  174. DrahtBot requested review from w0xlt on Jul 21, 2023
  175. DrahtBot removed the label CI failed on Jul 21, 2023
  176. DrahtBot removed review request from hernanmarino on Jul 23, 2023
  177. DrahtBot removed review request from w0xlt on Jul 23, 2023
  178. DrahtBot requested review from hernanmarino on Jul 23, 2023
  179. DrahtBot requested review from w0xlt on Jul 23, 2023
  180. in src/test/result_tests.cpp:100 in 4d995d3fa6 outdated
     96 }
     97 
     98+util::Result<std::string, FnError> StrFailFn(int i, bool success)
     99+{
    100+    auto result = IntFailFn(i, success);
    101+    if (!success) return {std::move(result), util::Error{Untranslated("str error")}, result.GetFailure()};
    


    TheCharlatan commented at 2:57 pm on July 27, 2023:
    It strikes me as unfortunate that the move constructor cannot move the failure values across different result value types, meaning the failure needs to be passed in as a separate argument. At the same time the user would be allowed to return a {std::move(result), util::Error{Untranslated("str error")}, potentially without the user noticing that this will not move the failure value ~and instead initialize a Monostate failure~ and instead default construct the failure value.

    ryanofsky commented at 5:46 pm on August 1, 2023:

    re: #25665 (review)

    It strikes me as unfortunate that the move constructor cannot move the failure values across different result value types, meaning the failure needs to be passed in as a separate argument. At the same time the user would be allowed to return a {std::move(result), util::Error{Untranslated("str error")}, potentially without the user noticing that this will not move the failure value ~and instead initialize a Monostate failure~ and instead default construct the failure value.

    Yes agree that would have been misleading. This should be fixed with the new util::Messages helper. Now just passing a bare std::move(result) is no longer allowed, so it shouldn’t look like the failure value is being moved. If you want to return a value from a Result<T1, F> function using a Result<T2, F> result value, now you have to write:

    0return {util::Error{}, util::Messages(std::move(result), result.GetFailure()};
    

    • EDIT: After the latest push, util::Messages is replaced by util::MoveMessages so this is now:

      0return {util::Error{}, util::MoveMessages(result), result.GetFailure()};
      

    I’m not sure how common it will be to have functions calling each other that have different success types but the same failure type. If it does turn out to be common, it should be possible to add syntax sugar so that can be shortened to:

    0return {util::Error{}, std::move(result)};
    
  181. DrahtBot removed review request from hernanmarino on Jul 27, 2023
  182. DrahtBot removed review request from w0xlt on Jul 27, 2023
  183. DrahtBot requested review from hernanmarino on Jul 27, 2023
  184. DrahtBot requested review from w0xlt on Jul 27, 2023
  185. DrahtBot removed review request from hernanmarino on Jul 27, 2023
  186. DrahtBot removed review request from w0xlt on Jul 27, 2023
  187. DrahtBot requested review from hernanmarino on Jul 27, 2023
  188. DrahtBot requested review from w0xlt on Jul 27, 2023
  189. in src/util/result.h:230 in 775b54e881 outdated
    229+    // Result::Set() method instead to set a result value while keeping any
    230+    // existing errors and warnings.
    231+    template <typename O>
    232+    Result& operator=(O&& other) = delete;
    233+
    234+    Result& Set(Result&& other) LIFETIMEBOUND
    


    stickies-v commented at 2:02 pm on July 28, 2023:
    Would it make sense to rename this to emplace, to keep the interface in line with optional and expected?

    ryanofsky commented at 5:46 pm on August 1, 2023:

    re: #25665 (review)

    Would it make sense to rename this to emplace, to keep the interface in line with optional and expected?

    This isn’t an actually an emplace method, it’s an assignment method. An emplace method for a Result<T> object would take arguments that would be accepted by one of T’s constructors, and construct a new T object in place with them.

    By contrast, this method doesn’t accept arguments that could be forwarded to a T constructor. Instead this method takes a Result<T> argument, and is basically the same as an operator=(Result&&) method.

    Added better documentation about this in the “multiple error and warning messages” commit:

    0    //! Move success or failure values from another result object to this
    1    //! object. Also move any error and warning messages from the other result
    2    //! object to this one. If this result object has an existing success or
    3    //! failure value it is cleared and replaced by the other value. If this
    4    //! result object has any error or warning messages, they are not cleared
    5    //! the messages will accumulate.
    6    Result& Set(Result&& other) LIFETIMEBOUND
    
  190. in src/util/result.h:102 in 775b54e881 outdated
     98+public:
     99+    //! std::optional methods, so functions returning optional<T> can change to
    100+    //! return Result<T> with minimal changes to existing code, and vice versa.
    101+    bool has_value() const { return bool{*this}; }
    102+    const T& value() const LIFETIMEBOUND { assert(*this); return m_value; }
    103+    T& value() LIFETIMEBOUND { assert(*this); return m_value; }
    


    stickies-v commented at 2:06 pm on July 28, 2023:

    nit: would it be more idiomatic to use has_value() here?

    0    const T& value() const LIFETIMEBOUND { assert(has_value()); return m_value; }
    1    T& value() LIFETIMEBOUND { assert(has_value()); return m_value; }
    

    ryanofsky commented at 5:45 pm on August 1, 2023:

    re: #25665 (review)

    nit: would it be more idiomatic to use has_value() here?

    I don’t think it’s more idiomatic but the suggestion seems fine so I adopted it.

  191. in src/util/result.h:116 in 775b54e881 outdated
    47+//! information and provides accessor methods.
    48+template <typename F>
    49+class ResultBase<void, F>
    50+{
    51+protected:
    52+    std::unique_ptr<ErrorInfo<F>> m_info;
    


    stickies-v commented at 2:44 pm on July 28, 2023:

    nit: would m_error or (m_error_info) be a more appropriate name? E.g. in bool(), the meaning of !m_error is much more intuitive at first sight compared to !m_info, I think? (i.e. it’s not really clear what it means to “not have info”, whereas “not have error” is clear).

    0explicit operator bool() const { return !m_info; }
    

    ryanofsky commented at 5:45 pm on August 1, 2023:

    re: #25665 (review)

    nit: would m_error or (m_error_info) be a more appropriate name? E.g. in bool(), the meaning of !m_error is much more intuitive at first sight compared to !m_info, I think? (i.e. it’s not really clear what it means to “not have info”, whereas “not have error” is clear).

    Calling it m_error would be misleading in later commits. The purpose of this field isn’t to indicate the presence of an error. It happens to do that temporarily in the “Add util::Result failure values” commit, but in later commit “Add util::Result multiple error and warning messages”, it can hold error and warning messages even in the success state.

  192. stickies-v commented at 4:55 pm on July 28, 2023: contributor
    Will continue my (re-)review next week, this is mostly up until 332e847c9ec0241efd9681eee3b03ff819aaddc3
  193. DrahtBot removed review request from hernanmarino on Jul 28, 2023
  194. DrahtBot removed review request from w0xlt on Jul 28, 2023
  195. DrahtBot requested review from hernanmarino on Jul 28, 2023
  196. DrahtBot requested review from stickies-v on Jul 28, 2023
  197. DrahtBot requested review from w0xlt on Jul 28, 2023
  198. DrahtBot removed review request from hernanmarino on Jul 28, 2023
  199. DrahtBot removed review request from w0xlt on Jul 28, 2023
  200. DrahtBot requested review from hernanmarino on Jul 28, 2023
  201. DrahtBot requested review from w0xlt on Jul 28, 2023
  202. DrahtBot removed review request from hernanmarino on Jul 28, 2023
  203. DrahtBot removed review request from w0xlt on Jul 28, 2023
  204. DrahtBot requested review from hernanmarino on Jul 28, 2023
  205. DrahtBot requested review from w0xlt on Jul 28, 2023
  206. ryanofsky force-pushed on Aug 1, 2023
  207. ryanofsky commented at 8:26 pm on August 1, 2023: contributor

    Updated 775b54e88107b0b976bf995e607926013fa9bc42 -> 1de05ef9190202f04f8cbe7746a47cbd66ab540c (pr/bresult2.35 -> pr/bresult2.36, compare) making suggested changes

    I still want to do more work to make the result class to enforce more safety with bool/optional/pointer types as discussed #25722 (review), and work better with the ResultPtr class from #26022. I also want to write better documentation with usage examples. So I’ll keep working on this, and push more changes here or in followup PRs.

  208. DrahtBot added the label CI failed on Aug 1, 2023
  209. ryanofsky force-pushed on Aug 1, 2023
  210. ryanofsky commented at 10:37 pm on August 1, 2023: contributor
    Updated 1de05ef9190202f04f8cbe7746a47cbd66ab540c -> 08f5febfc571220043436bbec96a326beebdee22 (pr/bresult2.36 -> pr/bresult2.37, compare) replacing util::Messages with util::MoveMessages to work around clang-tidy error bugprone-use-after-move (https://cirrus-ci.com/task/6657022251237376?logs=ci#L3119). This makes usage less verbose in most cases, too.
  211. DrahtBot removed the label CI failed on Aug 2, 2023
  212. in src/util/result.h:184 in 08f5febfc5 outdated
    183+    {
    184+        this->AddError(std::move(error.message));
    185+        Construct</*Failure=*/true>(std::forward<Args>(args)...);
    186+    }
    187+
    188+    // Recursive Construct() function. Peel off earning argument and call the next Construct().
    


    jonatack commented at 10:56 pm on August 2, 2023:
    Here and line 192 below: “earning”?

    ryanofsky commented at 7:26 pm on August 3, 2023:

    re: #25665 (review)

    Here and line 192 below: “earning”?

    Thanks, rewrote these comments now

  213. in src/node/chainstate.cpp:221 in 08f5febfc5 outdated
    187@@ -187,9 +188,9 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
    188     // Load a chain created from a UTXO snapshot, if any exist.
    189     chainman.DetectSnapshotChainstate(options.mempool);
    190 
    191-    auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options);
    192-    if (init_status != ChainstateLoadStatus::SUCCESS) {
    193-        return {init_status, init_error};
    194+    auto result{CompleteChainstateInitialization(chainman, cache_sizes, options)};
    195+    if (!result) {
    196+        return result;
    


    jonatack commented at 11:22 pm on August 2, 2023:
    584e3fa Not sure, but these if (!result) return result; idioms (here and lines 228-229 below) seem “odd” enough that an explanatory comment might be helpful.

    ryanofsky commented at 7:20 pm on August 3, 2023:

    re: #25665 (review)

    584e3fa Not sure, but these if (!result) return result; idioms (here and lines 228-229 below) seem “odd” enough that an explanatory comment might be helpful.

    I think this will probably be a common pattern and it would be too verbose to explain what is happening each place it is used. If the code were misleading, I think I would want to do something to fix it now. But if it’s just a little unusual looking, I think that’s expected at this point, and we can see what improvements would be useful in the future as it is used more widely.

  214. in src/util/result.h:30 in 08f5febfc5 outdated
    26+bilingual_str JoinMessages(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings);
    27+
    28+//! Helper function to move messages from one vector to another.
    29+void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest);
    30+
    31+//! Subsitute for std::monostate that doesn't depend on std::variant.
    


    jonatack commented at 11:29 pm on August 2, 2023:
    0//! Substitute for std::monostate that doesn't depend on std::variant.
    

    0src/util/result.h:30: Subsitute ==> Substitute
    1src/util/result.h:200: OT ==> TO, OF, OR, NOT
    2src/util/result.h:201: OT ==> TO, OF, OR, NOT
    3src/util/result.h:221: OT ==> TO, OF, OR, NOT
    4src/util/result.h:222: OT ==> TO, OF, OR, NOT
    5^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in test/lint/spelling.ignore-words.txt
    

    ryanofsky commented at 7:21 pm on August 3, 2023:

    re: #25665 (review)

    also, per the lint CI / spelling linter

    Thanks, fixed spelling

  215. in src/test/result_tests.cpp:6 in 08f5febfc5 outdated
    1@@ -2,6 +2,7 @@
    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/check.h>
    


    jonatack commented at 11:32 pm on August 2, 2023:
    0+#include <tinyformat.h>
    1+#include <util/translation.h3
    2
    3+#include <algorithm>
    4+#include <memory>
    5+#include <ostream>
    6+#include <string>
    7+#include <utility>
    

    ryanofsky commented at 7:20 pm on August 3, 2023:

    re: #25665 (review)

    Tidy CI iwyu suggestions

    Thanks applied these

  216. in src/util/result.h:209 in 08f5febfc5 outdated
    70+    explicit operator bool() const { return !m_info || !m_info->failure; }
    71+
    72+    //! Error retrieval.
    73+    const auto& GetFailure() const LIFETIMEBOUND { assert(!*this); return *m_info->failure; }
    74+    const std::vector<bilingual_str>& GetErrors() const LIFETIMEBOUND { return m_info ? m_info->errors : EMPTY_LIST; }
    75+    const std::vector<bilingual_str>& GetWarnings() const LIFETIMEBOUND { return m_info ? m_info->warnings : EMPTY_LIST; }
    


    jonatack commented at 11:40 pm on August 2, 2023:
    6ddcc9be GetErrors() and GetWarnings() don’t seem to have any test coverage yet. Note that there is also already a GetWarnings() in src/warnings.{h.cpp}, perhaps disambiguate naming/grepping-wise in addition to namespace-wise.

    ryanofsky commented at 7:26 pm on August 3, 2023:

    re: #25665 (review)

    6ddcc9b GetErrors() and GetWarnings() don’t seem to have any test coverage yet. Note that there is also already a GetWarnings() in src/warnings.{h.cpp}, perhaps disambiguate naming/grepping-wise in addition to namespace-wise.

    Thanks, added some test coverage. On the warnings.h overlap, I think that GetWarnings() is not a great name for a top level function, and that function would be better to rename (or delete) regardless.

  217. jonatack commented at 11:45 pm on August 2, 2023: contributor

    ACK 08f5febfc571220043436bbec96a326beebdee22

    Some non-blocking comments follow.

  218. DrahtBot removed review request from hernanmarino on Aug 2, 2023
  219. DrahtBot removed review request from w0xlt on Aug 2, 2023
  220. DrahtBot requested review from hernanmarino on Aug 2, 2023
  221. DrahtBot requested review from w0xlt on Aug 2, 2023
  222. DrahtBot removed review request from hernanmarino on Aug 2, 2023
  223. DrahtBot removed review request from w0xlt on Aug 2, 2023
  224. DrahtBot requested review from hernanmarino on Aug 2, 2023
  225. DrahtBot requested review from w0xlt on Aug 2, 2023
  226. ryanofsky force-pushed on Aug 3, 2023
  227. ryanofsky commented at 8:42 pm on August 3, 2023: contributor
    Updated 08f5febfc571220043436bbec96a326beebdee22 -> b0beb4c504da29c27358d4602a045aaab39305f6 (pr/bresult2.37 -> pr/bresult2.38, compare) moving a lot more functionality from the Result class to the ResultBase class so the new code can be compatible with the ResultPtr class from #26022. Also rewrote and added more documentation and implemented latest review suggestions.
  228. DrahtBot added the label CI failed on Aug 3, 2023
  229. ryanofsky force-pushed on Aug 4, 2023
  230. ryanofsky commented at 11:26 am on August 4, 2023: contributor
    Updated b0beb4c504da29c27358d4602a045aaab39305f6 -> 9e80d0b754a28733c79a52c8e0431616c31d071c (pr/bresult2.38 -> pr/bresult2.39, compare) to fix operator>> compile error that seemed to happen with newer versions of clang: https://cirrus-ci.com/task/4622529512341504?logs=ci#L2370
  231. DrahtBot removed the label CI failed on Aug 4, 2023
  232. in src/util/result.h:85 in 9e80d0b754 outdated
     98+template <class Result>
     99+MoveMessages(Result&& result) -> MoveMessages<Result&&>;
    100+
    101+namespace detail {
    102+//! Empty string list
    103+const std::vector<bilingual_str> EMPTY_LIST{};
    


    jonatack commented at 4:53 pm on August 4, 2023:
    835f094 It looks like this empty vector might be declarable constexpr, at least with clang 16.0.6 arm64. Possibly not uniformly until C++20.

    ryanofsky commented at 2:54 pm on August 7, 2023:

    re: #25665 (review)

    835f094 It looks like this empty vector might be declarable constexpr, at least with clang 16.0.6 arm64. Possibly not uniformly until C++20.

    This is a good idea, but I think you are right it requires c++20. With my compiler (clang 13) I see:

    0./util/result.h:85:38: error: constexpr variable cannot have non-literal type 'const std::vector<bilingual_str>'
    1constexpr std::vector<bilingual_str> EMPTY_LIST{};
    2                                     ^
    3/nix/store/acbklvmaxi32lj3f7k1m1y00017f89ix-gcc-11.3.0/include/c++/11.3.0/bits/stl_vector.h:389:11: note: 'vector<bilingual_str>' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors
    4    class vector : protected _Vector_base<_Tp, _Alloc>
    
  233. in src/util/result.h:286 in 9e80d0b754 outdated
    282+class Result : public detail::ResultBase<T, F>
    283 {
    284-    return result ? bilingual_str{} : std::get<0>(result.m_variant);
    285-}
    286+protected:
    287+    using Base = detail::ResultBase<T, F>;
    


    jonatack commented at 4:54 pm on August 4, 2023:
    9ec1bda Can Base here be private instead of protected?

    ryanofsky commented at 6:01 pm on August 4, 2023:

    re: #25665 (review)

    9ec1bda Can Base here be private instead of protected?

    It could, but in general I prefer to use protected over private when it is safe for extensibility and testing, and in this case I want to support adding a ResultPtr type like the one in #26022 which inherits from Result. Also, Base is just a type alias for a public type, so making it private could only make the alias private, not the type, and only force type declarations to be more verbose.

    Conceptually, I think the inheritance used in result.h is basically just an implementation detail, and that ResultBase/Result/ResultPtr classes should act like a single class and have full access to result state without any unnecessary obstacles or verbosity or layers of abstraction. In the future it would even be nice to combine Result and ResultBase classes together using c++23’s deduced this or maybe doing it without waiting for c++23 using the CRTP pattern mentioned at that link.

  234. in src/util/result.h:301 in 9e80d0b754 outdated
    301+    }
    302+
    303+    //! Move-construct a Result object from another Result object, moving the
    304+    //! success or failure value any any error or warning messages.
    305+    template <typename OT, typename OF>
    306+    Result(Result<OT, OF>&& other)
    


    jonatack commented at 5:05 pm on August 4, 2023:

    9ec1bda

    0src/util/result.h:300: OT ==> TO, OF, OR, NOT
    1src/util/result.h:301: OT ==> TO, OF, OR, NOT
    2^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in test/lint/spelling.ignore-words.txt
    

    ryanofsky commented at 5:22 pm on August 7, 2023:

    re: #25665 (review)

    0^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in test/lint/spelling.ignore-words.txt
    

    Thanks, added

  235. jonatack commented at 5:08 pm on August 4, 2023: contributor

    ACK 9e80d0b754a28733c79a52c8e0431616c31d071c

    Nice documentation, code, and commit message improvements. Some non-blocking comments. Consider also making swap/move/destructors in Result/ResultBase noexcept. FWIW I didn’t hit the CI build error in the previous push with arm64 clang 16.0.6.

  236. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  237. DrahtBot removed review request from w0xlt on Aug 4, 2023
  238. DrahtBot requested review from hernanmarino on Aug 4, 2023
  239. DrahtBot requested review from w0xlt on Aug 4, 2023
  240. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  241. DrahtBot removed review request from w0xlt on Aug 4, 2023
  242. DrahtBot requested review from hernanmarino on Aug 4, 2023
  243. DrahtBot requested review from w0xlt on Aug 4, 2023
  244. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  245. DrahtBot removed review request from w0xlt on Aug 4, 2023
  246. DrahtBot requested review from hernanmarino on Aug 4, 2023
  247. DrahtBot requested review from w0xlt on Aug 4, 2023
  248. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  249. DrahtBot removed review request from w0xlt on Aug 4, 2023
  250. DrahtBot requested review from hernanmarino on Aug 4, 2023
  251. DrahtBot requested review from w0xlt on Aug 4, 2023
  252. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  253. DrahtBot removed review request from w0xlt on Aug 4, 2023
  254. DrahtBot requested review from hernanmarino on Aug 4, 2023
  255. DrahtBot requested review from w0xlt on Aug 4, 2023
  256. DrahtBot removed review request from hernanmarino on Aug 4, 2023
  257. DrahtBot removed review request from w0xlt on Aug 4, 2023
  258. DrahtBot requested review from hernanmarino on Aug 4, 2023
  259. DrahtBot requested review from w0xlt on Aug 4, 2023
  260. ryanofsky force-pushed on Aug 7, 2023
  261. ryanofsky commented at 6:06 pm on August 7, 2023: contributor
    Updated 9e80d0b754a28733c79a52c8e0431616c31d071c -> 7f883b33bb89205a9d00c2d20d363a36a0167c7c (pr/bresult2.39 -> pr/bresult2.40, compare) responding to new review comments and also making some internal changes within the PR to reduce unnecessary diffs between commits.
  262. jonatack commented at 6:39 pm on August 7, 2023: contributor
    ACK 7f883b33bb89205a9d00c2d20d363a36a0167c7c
  263. DrahtBot removed review request from hernanmarino on Aug 7, 2023
  264. DrahtBot removed review request from w0xlt on Aug 7, 2023
  265. DrahtBot requested review from hernanmarino on Aug 7, 2023
  266. DrahtBot requested review from w0xlt on Aug 7, 2023
  267. in src/util/result.h:331 in 7f883b33bb outdated
    331+    //!    auto r1 = DoSomething() >> result;
    332+    //!    auto r2 = DoSomethingElse() >> result;
    333+    //!    ...
    334+    //!    return result;
    335+    //!
    336+    template<typename O>
    


    TheCharlatan commented at 1:36 pm on August 11, 2023:
    Style nit: Missing space.

    ryanofsky commented at 7:20 pm on September 5, 2023:

    re: #25665 (review)

    Style nit: Missing space.

    Thanks, fixed

  268. in src/util/result.h:181 in 7f883b33bb outdated
    197+    {
    198+        if (Constructed) {
    199+            if (dst) {
    200+                dst.DestroyValue();
    201+            } else {
    202+                dst.m_info->failure.reset();
    


    TheCharlatan commented at 7:30 am on August 29, 2023:

    This line is missing test coverage. It could be achieved with the following diff (though there might be a more elegant way to achieve this):

     0-util::Result<int, FnError> AccumulateFn(bool success)
     1+util::Result<int, FnError> AccumulateFn(bool success, bool init_success)
     2 {
     3-    util::Result<int, FnError> result;
     4+    util::Result<int, FnError> result = IntFailFn(0, init_success);
     5     util::Result<int> x = MultiWarnFn(1) >> result;
     6     BOOST_REQUIRE(x);
     7     util::Result<int> y = MultiWarnFn(2) >> result;
     8@@ -195,8 +195,10 @@ BOOST_AUTO_TEST_CASE(check_returned)
     9     ExpectFail(EnumFailFn(ERR2), Untranslated("enum fail."), ERR2);
    10     ExpectFail(ChainedFailFn(ERR1, 5), Untranslated("chained fail. enum fail. warn."), 5);
    11     ExpectSuccess(MultiWarnFn(3), Untranslated("warn 0. warn 1. warn 2."), 3);
    12-    ExpectSuccess(AccumulateFn(true), Untranslated("warn 0. warn 0. warn 1. int 3 warn."), 3);
    13-    ExpectFail(AccumulateFn(false), Untranslated("int 3 error. warn 0. warn 0. warn 1."), ERR1);
    14+    ExpectSuccess(AccumulateFn(true, true), Untranslated("int 0 warn. warn 0. warn 0. warn 1. int 3 warn."), 3);
    15+    ExpectFail(AccumulateFn(false, true), Untranslated("int 3 error. int 0 warn. warn 0. warn 0. warn 1."), ERR1);
    16+    ExpectSuccess(AccumulateFn(true, false), Untranslated("int 0 error. warn 0. warn 0. warn 1. int 3 warn."), 3);
    17+    ExpectFail(AccumulateFn(false, false), Untranslated("int 0 error. int 3 error. warn 0. warn 0. warn 1."), ERR1);
    18     ExpectSuccess(TruthyFalsyFn(0, true), {}, 0);
    19     ExpectFail(TruthyFalsyFn(0, false), Untranslated("failure value 0."), 0);
    20     ExpectSuccess(TruthyFalsyFn(1, true), {}, 1);
    

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    This line is missing test coverage. It could be achieved with the following diff (though there might be a more elegant way to achieve this):

    Thanks, I added a test just calling the Set method to cover these branches. Suggestion to modify the AccumulateFn function instead was good, too, just seemed more complicated.

  269. TheCharlatan commented at 7:34 am on August 29, 2023: contributor
    ACK 7f883b33bb89205a9d00c2d20d363a36a0167c7c
  270. DrahtBot removed review request from hernanmarino on Aug 29, 2023
  271. DrahtBot removed review request from w0xlt on Aug 29, 2023
  272. DrahtBot requested review from hernanmarino on Aug 29, 2023
  273. DrahtBot requested review from w0xlt on Aug 29, 2023
  274. DrahtBot removed review request from hernanmarino on Aug 29, 2023
  275. DrahtBot removed review request from w0xlt on Aug 29, 2023
  276. DrahtBot requested review from hernanmarino on Aug 29, 2023
  277. DrahtBot requested review from w0xlt on Aug 29, 2023
  278. achow101 commented at 6:51 pm on August 29, 2023: member
    ACK 7f883b33bb89205a9d00c2d20d363a36a0167c7c
  279. achow101 commented at 6:54 pm on August 29, 2023: member

    Silent merge conflict:

     0In file included from ../../../src/wallet/coinselection.h:16,
     1                 from ../../../src/wallet/test/fuzz/coinselection.cpp:12:
     2../../../src/util/result.h: In instantiation of void util::detail::ResultBase<T, F>::ConstructValue(Args&& ...) [with Args = {util::Result<wallet::SelectionResult, void>&}; T = wallet::SelectionResult; F = void]:
     3../../../src/util/result.h:141:34:   required from static void util::detail::ResultBase<void, F>::ConstructResult(Result&, Args&& ...) [with bool Failure = false; Result = util::Result<wallet::SelectionResult>; Args = {util::Result<wallet::SelectionResult, void>&}; F = void]
     4../../../src/util/result.h:295:58:   required from util::Result<T, F>::Result(Args&& ...) [with Args = {util::Result<wallet::SelectionResult, void>&}; T = wallet::SelectionResult; F = void]
     5../../../src/wallet/test/fuzz/coinselection.cpp:152:95:   required from here
     6../../../src/util/result.h:243:43: error: no matching function for call to wallet::SelectionResult::SelectionResult(<brace-enclosed initializer list>)
     7  243 |     void ConstructValue(Args&&... args) { new (&m_value) T{std::forward<Args>(args)...}; }
     8      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     9../../../src/wallet/coinselection.h:352:14: note: candidate: wallet::SelectionResult::SelectionResult(CAmount, wallet::SelectionAlgorithm)
    10  352 |     explicit SelectionResult(const CAmount target, SelectionAlgorithm algo)
    11      |              ^~~~~~~~~~~~~~~
    12../../../src/wallet/coinselection.h:352:14: note:   candidate expects 2 arguments, 1 provided
    13../../../src/wallet/coinselection.h:324:8: note: candidate: wallet::SelectionResult::SelectionResult(const wallet::SelectionResult&)
    14  324 | struct SelectionResult
    15      |        ^~~~~~~~~~~~~~~
    16../../../src/wallet/coinselection.h:324:8: note:   no known conversion for argument 1 from util::Result<wallet::SelectionResult> to const wallet::SelectionResult&
    17../../../src/wallet/coinselection.h:324:8: note: candidate: wallet::SelectionResult::SelectionResult(wallet::SelectionResult&&)
    18../../../src/wallet/coinselection.h:324:8: note:   no known conversion for argument 1 from util::Result<wallet::SelectionResult> to wallet::SelectionResult&&
    19In file included from /usr/include/c++/13.2.1/memory:69,
    20                 from ../../../src/serialize.h:17,
    21                 from ../../../src/policy/feerate.h:10,
    22                 from ../../../src/wallet/test/fuzz/coinselection.cpp:5:
    23/usr/include/c++/13.2.1/bits/stl_uninitialized.h: In instantiation of constexpr bool std::__check_constructible() [with _ValueType = util::Result<wallet::SelectionResult>; _Tp = const util::Result<wallet::SelectionResult>&]:
    24/usr/include/c++/13.2.1/bits/stl_uninitialized.h:182:4:   required from _ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const util::Result<wallet::SelectionResult>*; _ForwardIterator = util::Result<wallet::SelectionResult>*]
    25/usr/include/c++/13.2.1/bits/stl_uninitialized.h:373:37:   required from _ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, allocator<_Tp>&) [with _InputIterator = const util::Result<wallet::SelectionResult>*; _ForwardIterator = util::Result<wallet::SelectionResult>*; _Tp = util::Result<wallet::SelectionResult>]
    26/usr/include/c++/13.2.1/bits/stl_vector.h:1692:33:   required from void std::vector<_Tp, _Alloc>::_M_range_initialize(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const util::Result<wallet::SelectionResult>*; _Tp = util::Result<wallet::SelectionResult>; _Alloc = std::allocator<util::Result<wallet::SelectionResult> >]
    27/usr/include/c++/13.2.1/bits/stl_vector.h:679:21:   required from std::vector<_Tp, _Alloc>::vector(std::initializer_list<_Tp>, const allocator_type&) [with _Tp = util::Result<wallet::SelectionResult>; _Alloc = std::allocator<util::Result<wallet::SelectionResult> >; allocator_type = std::allocator<util::Result<wallet::SelectionResult> >]
    28../../../src/wallet/test/fuzz/coinselection.cpp:152:95:   required from here
    29/usr/include/c++/13.2.1/bits/stl_uninitialized.h:90:56: error: static assertion failed: result type must be constructible from input type
    30   90 |       static_assert(is_constructible<_ValueType, _Tp>::value,
    31      |                                                        ^~~~~
    32/usr/include/c++/13.2.1/bits/stl_uninitialized.h:90:56: note: std::integral_constant<bool, false>::value evaluates to false
    33make[2]: *** [Makefile:16569: wallet/test/fuzz/test_fuzz_fuzz-coinselection.o] Error 1
    
  280. DrahtBot removed review request from hernanmarino on Aug 29, 2023
  281. DrahtBot removed review request from w0xlt on Aug 29, 2023
  282. DrahtBot requested review from hernanmarino on Aug 29, 2023
  283. DrahtBot requested review from w0xlt on Aug 29, 2023
  284. DrahtBot removed review request from hernanmarino on Aug 29, 2023
  285. DrahtBot removed review request from w0xlt on Aug 29, 2023
  286. DrahtBot requested review from hernanmarino on Aug 29, 2023
  287. DrahtBot requested review from w0xlt on Aug 29, 2023
  288. DrahtBot added the label CI failed on Aug 29, 2023
  289. in src/util/result.h:72 in 03595104cc outdated
    220-        assert(has_value());
    221-        return std::get<1>(m_variant);
    222-    }
    223-    T& value() LIFETIMEBOUND
    224-    {
    225-        assert(has_value());
    


    maflcko commented at 1:24 pm on August 30, 2023:

    ryanofsky commented at 7:20 pm on September 5, 2023:

    re: #25665 (review)

    in 0359510: clang-format new code to reduce this diff here?

    I can do it if there’s another request, but I think simple accessor methods are more readable if declarations are on the left, implementations are on the right, to make it easier to find relevant information and notice differences between functions.

  290. in src/util/result.h:129 in 03595104cc outdated
    145+    //! message from one result object to another. The two result
    146+    //! objects must have compatible success and failure types.
    147+    template <bool Constructed, typename DstResult, typename SrcResult>
    148+    static void MoveResult(DstResult& dst, SrcResult&& src)
    149+    {
    150+        if (Constructed) {
    


    maflcko commented at 1:29 pm on August 30, 2023:
    in https://github.com/bitcoin/bitcoin/commit/03595104ccda64f301cac217d7de9aae69c0de67: Doesn’t matter, but could add contexpr here, or remove it above, for consistency, if it compiles?

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    in 0359510: Doesn’t matter, but could add contexpr here, or remove it above, for consistency, if it compiles?

    Good catch, done.

  291. in src/util/result.h:317 in 03595104cc outdated
    238+        Base::template MoveResult</*Constructed=*/false>(*this, std::move(other));
    239+    }
    240+
    241+    //! Move success or failure values and any error message from another Result
    242+    //! object to this object.
    243+    Result& Set(Result&& other) LIFETIMEBOUND
    


    maflcko commented at 1:32 pm on August 30, 2023:
    in https://github.com/bitcoin/bitcoin/commit/03595104ccda64f301cac217d7de9aae69c0de67: Is there a unit test for this method? I am thinking about a test to check what happens when an error-result is Set a value-result, and vice-versa.

    ryanofsky commented at 7:20 pm on September 5, 2023:

    re: #25665 (review)

    in 0359510: Is there a unit test for this method? I am thinking about a test to check what happens when an error-result is Set a value-result, and vice-versa.

    Good suggestion. Added a test to exercise the Set() method with success & failure values.

  292. in src/util/result.h:241 in 03595104cc outdated
    244+    {
    245+        Base::template MoveResult</*Constructed=*/true>(*this, std::move(other));
    246+        return *this;
    247+    }
    248+
    249+    inline friend bilingual_str _ErrorString(const Result& result)
    


    maflcko commented at 1:33 pm on August 30, 2023:

    in https://github.com/bitcoin/bitcoin/commit/03595104ccda64f301cac217d7de9aae69c0de67: Why inline? IIUC every member is inline, so this seems confusing, no?

    From the usage below it looks like this is static for some reason? Is inline friend an alias for static friend?


    ryanofsky commented at 7:31 pm on September 5, 2023:

    re: #25665 (review)

    in 0359510: Why inline? IIUC every member is inline, so this seems confusing, no?

    This is just a friend function that is able to access private fields of the Result class, not a member. So I think inline was required to avoid duplicate symbol errors from the linker. I would have preferred to just make the ErrorString function a friend function directly, but this did not seem to be possible because it has template parameters, so I declared _ErrorString to be a friend function which can called by ErrorString to specify the template parameters.

    This complexity goes away later when GetWarnings() / GetErrors() accessors are added. My main goal for all this is just to make the Result class a container for error strings, and keep any code dealing with the strings separate.

  293. in src/util/result.h:39 in 03595104cc outdated
    35+//!    void TryAddNumbers(int a, int b)
    36+//!    {
    37+//!        if (auto result = AddNumbers(a, b)) {
    38+//!            printf("%i + %i = %i\n", a, b, *result);
    39+//!        } else {
    40+//!            printf("Error: %s\n", util::ErrorString(result).translated.c_str());
    


    maflcko commented at 1:36 pm on August 30, 2023:
    in https://github.com/bitcoin/bitcoin/commit/03595104ccda64f301cac217d7de9aae69c0de67: Obviously doesn’t matter, but if there is an alternative to say the same without c_str(), I’d prefer that :sweat_smile:

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    in 0359510: Obviously doesn’t matter, but if there is an alternative to say the same without c_str(), I’d prefer that 😅

    Makes sense, switched to LogPrintf

  294. in src/util/result.cpp:8 in b21e2b067e outdated
    0@@ -0,0 +1,31 @@
    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 <algorithm>
    6+#include <initializer_list>
    7+#include <iterator>
    8+#include <util/result.h>
    


    maflcko commented at 1:57 pm on August 30, 2023:
    nit in b21e2b067e28e49a289e55a1bd25f67a7c342f76: The self-include must be in the first line and first section to catch missing includes.

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    nit in b21e2b0: The self-include must be in the first line and first section to catch missing includes.

    Makes sense, moved

  295. in src/util/result.cpp:1 in b21e2b067e outdated
    0@@ -0,0 +1,31 @@
    1+// Copyright (c) 2022 The Bitcoin Core developers
    


    maflcko commented at 1:57 pm on August 30, 2023:
    nit in b21e2b067e28e49a289e55a1bd25f67a7c342f76: 2022-present or no year for new code to avoid having to ever touch this again?

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    nit in b21e2b0: 2022-present or no year for new code to avoid having to ever touch this again?

    Just leaving alone for now. The “present” idea is interesting but doesn’t seem right. I’d expect “present” to mean “when I’m writing this,” or “when you’re reading this,” not “when this code was last changed,” which is what we want. Would be happy to drop the year or drop the whole line though.

  296. in src/util/result.h:81 in b21e2b067e outdated
    77+    Result& m_result;
    78+};
    79+
    80+//! Template deduction guide for MoveMessages class.
    81+template <class Result>
    82+MoveMessages(Result&& result) -> MoveMessages<Result&&>;
    


    maflcko commented at 2:02 pm on August 30, 2023:
    nit in https://github.com/bitcoin/bitcoin/commit/b21e2b067e28e49a289e55a1bd25f67a7c342f76: Are the && on the right side needed? IIUC this should only affect Result& m_result;, which should be equal to Result && & m_result;, no?

    ryanofsky commented at 7:19 pm on September 5, 2023:

    re: #25665 (review)

    nit in b21e2b0: Are the && on the right side needed? IIUC this should only affect Result& m_result;, which should be equal to Result && & m_result;, no?

    Good catch, dropped this.

  297. in src/util/result.h:207 in b21e2b067e outdated
    198@@ -152,10 +199,35 @@ class ResultBase<void, F>
    199 
    200 public:
    201     //! Success check.
    202-    explicit operator bool() const { return !m_info; }
    203+    explicit operator bool() const { return !m_info || !m_info->failure; }
    204 
    205     //! Error retrieval.
    206-    const auto& GetFailure() const LIFETIMEBOUND { assert(!*this); return m_info->failure; }
    207+    const auto& GetFailure() const LIFETIMEBOUND { assert(!*this); return *m_info->failure; }
    


    maflcko commented at 2:07 pm on August 30, 2023:

    ryanofsky commented at 7:20 pm on September 5, 2023:

    re: #25665 (review)

    nit in b21e2b0: Missing assert(m_info) to avoid UB, no?

    This won’t be UB unless the assert is disabled because the assert is checking that m_info is non-null and contains a failure value (see operator bool above)

  298. maflcko approved
  299. maflcko commented at 2:11 pm on August 30, 2023: member

    left some nits/questions. Feel free to ignore.

    review ACK 7f883b33bb89205a9d00c2d20d363a36a0167c7c 🕳

    Signature:

    0untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
    1RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
    2trusted comment: review ACK 7f883b33bb89205a9d00c2d20d363a36a0167c7c 🕳
    3DXdFmPbNLbWwTTSe/r0eJM/R9zvFrppA/gKmtEb5GH8jQwPbsPAsyPCB50Bm3n6kNKRUxcLFgHTIPbfDn0E9CQ==
    
  300. DrahtBot removed review request from hernanmarino on Aug 30, 2023
  301. DrahtBot removed review request from w0xlt on Aug 30, 2023
  302. DrahtBot requested review from w0xlt on Aug 30, 2023
  303. DrahtBot requested review from hernanmarino on Aug 30, 2023
  304. DrahtBot removed review request from w0xlt on Aug 30, 2023
  305. DrahtBot removed review request from hernanmarino on Aug 30, 2023
  306. DrahtBot requested review from hernanmarino on Aug 30, 2023
  307. DrahtBot requested review from w0xlt on Aug 30, 2023
  308. achow101 added the label Needs rebase on Sep 5, 2023
  309. DrahtBot removed the label Needs rebase on Sep 5, 2023
  310. ryanofsky force-pushed on Sep 6, 2023
  311. ryanofsky commented at 6:02 pm on September 6, 2023: contributor
    Rebased 7f883b33bb89205a9d00c2d20d363a36a0167c7c -> 956bec1ecadcfef13e16f1364ae3a7043ff50e48 (pr/bresult2.40 -> pr/bresult2.41, compare) to fix silent conflict with #27585, and made many suggested improvements
  312. TheCharlatan commented at 7:19 pm on September 6, 2023: contributor
    Nice, Re-ACK 956bec1ecadcfef13e16f1364ae3a7043ff50e48
  313. DrahtBot removed review request from hernanmarino on Sep 6, 2023
  314. DrahtBot removed review request from w0xlt on Sep 6, 2023
  315. DrahtBot requested review from hernanmarino on Sep 6, 2023
  316. DrahtBot requested review from w0xlt on Sep 6, 2023
  317. DrahtBot requested review from maflcko on Sep 6, 2023
  318. DrahtBot requested review from achow101 on Sep 6, 2023
  319. DrahtBot requested review from jonatack on Sep 6, 2023
  320. DrahtBot removed the label CI failed on Sep 7, 2023
  321. DrahtBot added the label CI failed on Oct 5, 2023
  322. maflcko commented at 4:20 pm on October 14, 2023: member
    Needs rebase if still relevant
  323. DrahtBot removed review request from hernanmarino on Oct 14, 2023
  324. DrahtBot removed review request from w0xlt on Oct 14, 2023
  325. DrahtBot requested review from hebasto on Oct 14, 2023
  326. DrahtBot requested review from w0xlt on Oct 14, 2023
  327. DrahtBot requested review from hernanmarino on Oct 14, 2023
  328. ryanofsky force-pushed on Oct 18, 2023
  329. ryanofsky commented at 7:27 pm on October 18, 2023: contributor
    Rebased 956bec1ecadcfef13e16f1364ae3a7043ff50e48 -> 29f6cfdabecbdecafc52ccc86425a1c7bb7f5c40 (pr/bresult2.41 -> pr/bresult2.42, compare) due to silent conflict with #27596
  330. DrahtBot removed the label CI failed on Oct 18, 2023
  331. TheCharlatan approved
  332. TheCharlatan commented at 6:01 am on October 19, 2023: contributor

    Re-ACK 29f6cfdabecbdecafc52ccc86425a1c7bb7f5c40

    Changes are resolving a simple silent conflict and adapting for the new clang-tidy lint case.

  333. DrahtBot removed review request from w0xlt on Oct 19, 2023
  334. DrahtBot removed review request from hernanmarino on Oct 19, 2023
  335. DrahtBot requested review from hernanmarino on Oct 19, 2023
  336. DrahtBot requested review from w0xlt on Oct 19, 2023
  337. maflcko commented at 9:11 am on October 25, 2023: member

    Did you compile each commit locally and ran the tests? See CI:

    0test/result_tests.cpp(112): error: in "result_tests/check_set": check util::ErrorString(result) == str has failed [bilingual_str('' , '') != bilingual_str('error' , 'error')]
    
  338. DrahtBot removed review request from hernanmarino on Oct 25, 2023
  339. DrahtBot removed review request from w0xlt on Oct 25, 2023
  340. DrahtBot requested review from hernanmarino on Oct 25, 2023
  341. DrahtBot requested review from w0xlt on Oct 25, 2023
  342. ryanofsky force-pushed on Oct 26, 2023
  343. ryanofsky commented at 0:54 am on October 26, 2023: contributor

    re: #25665 (comment)

    Did you compile each commit locally and ran the tests?

    Thanks, I didn’t realize the test was broken in earlier commits. I backported it further and got it working in all commits with no changes to the final diff.

    Updated 29f6cfdabecbdecafc52ccc86425a1c7bb7f5c40 -> f158686962e1a229d0382c739b78cd9644aa7ada (pr/bresult2.42 -> pr/bresult2.43, compare) fixing test failure in intermediate commit 96667abecbd9c0e185fb4914897cc6ec07b39d9c (https://github.com/bitcoin/bitcoin/actions/runs/6565613863/job/17834513251?pr=25665)

  344. TheCharlatan approved
  345. TheCharlatan commented at 11:34 am on October 26, 2023: contributor
    Re-ACK f158686962e1a229d0382c739b78cd9644aa7ada
  346. DrahtBot removed review request from hernanmarino on Oct 26, 2023
  347. DrahtBot removed review request from w0xlt on Oct 26, 2023
  348. DrahtBot requested review from w0xlt on Oct 26, 2023
  349. DrahtBot requested review from hernanmarino on Oct 26, 2023
  350. DrahtBot added the label Needs rebase on Dec 12, 2023
  351. Fabcien referenced this in commit e0df40da76 on Dec 15, 2023
  352. ryanofsky force-pushed on Jan 24, 2024
  353. DrahtBot removed the label Needs rebase on Jan 24, 2024
  354. TheCharlatan approved
  355. TheCharlatan commented at 7:35 pm on January 24, 2024: contributor
    Re-ACK f822ac9a24d684937f1258da89812e99c4b205ba
  356. DrahtBot removed review request from w0xlt on Jan 24, 2024
  357. DrahtBot removed review request from hernanmarino on Jan 24, 2024
  358. DrahtBot requested review from w0xlt on Jan 24, 2024
  359. DrahtBot requested review from hernanmarino on Jan 24, 2024
  360. hernanmarino approved
  361. hernanmarino commented at 4:41 pm on February 5, 2024: contributor
    re ack f822ac9a24d684937f1258da89812e99c4b205ba
  362. DrahtBot removed review request from w0xlt on Feb 5, 2024
  363. DrahtBot requested review from w0xlt on Feb 5, 2024
  364. DrahtBot requested review from hernanmarino on Feb 5, 2024
  365. TheCharlatan commented at 9:17 am on February 13, 2024: contributor
    I think there is a silent merge conflict on the first commit.
  366. DrahtBot removed review request from w0xlt on Feb 13, 2024
  367. DrahtBot removed review request from hernanmarino on Feb 13, 2024
  368. DrahtBot requested review from hernanmarino on Feb 13, 2024
  369. DrahtBot requested review from w0xlt on Feb 13, 2024
  370. maflcko commented at 10:57 am on February 13, 2024: member
    At the risk of having asked this previously: Why not postpone the 1c7d8be commit to a later pull? This would also make review easier, because the code change comes with the changes to actually use it in real code, outside of just unit tests.
  371. DrahtBot removed review request from hernanmarino on Feb 13, 2024
  372. DrahtBot removed review request from w0xlt on Feb 13, 2024
  373. DrahtBot requested review from w0xlt on Feb 13, 2024
  374. DrahtBot requested review from hernanmarino on Feb 13, 2024
  375. DrahtBot added the label CI failed on Feb 13, 2024
  376. DrahtBot commented at 6:23 pm on February 13, 2024: contributor

    🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the documentation.

    Possibly this is due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    Leave a comment here, if you need help tracking down a confusing failure.

    Debug: https://github.com/bitcoin/bitcoin/runs/20825711501

  377. ryanofsky commented at 6:06 pm on February 21, 2024: contributor

    At the risk of having asked this previously: Why not postpone the 1c7d8be commit to a later pull? This would also make review easier, because the code change comes with the changes to actually use it in real code, outside of just unit tests.

    Sure, I’m happy to split up this PR.

    I don’t think this idea came up before (or I can’t remember if it did). From my perspective commit 1c7d8bea4f0b25f9adb89e402a130fa114220494 is basically the point of this PR, and the other commits are less important. Usually when we have errors we want to return descriptive error strings and fail, not return error codes and branch. So I think the part of this PR that returns better error and warning messages is more interesting than the part that returns error codes.

    As far as using the changes in real code, not just tests, I opened #25722 at the same time as this PR to start using it throughout wallet code, so there are actually a lot of usages to look at outside of tests. The usage patterns in the test are also meant to be realistic, with test functions returning errors and warnings, accumulating them, and returning them as part of their own Results .

    I don’t think it hurts anything to split this PR up, so I can try that, but I’m also curious if other reviewers want me to split this or leave it alone. According to draftbot this has 1 current ack and 6 stale acks, and it seems like this needs rebase due to a silent conflict. So maybe the problem is more that it hasn’t gotten enough simultaneous acks at the same time, and that I’ve been lazy about rebasing, than that it should be split up.

  378. DrahtBot removed review request from w0xlt on Feb 21, 2024
  379. DrahtBot removed review request from hernanmarino on Feb 21, 2024
  380. DrahtBot requested review from w0xlt on Feb 21, 2024
  381. DrahtBot requested review from hernanmarino on Feb 21, 2024
  382. DrahtBot removed review request from w0xlt on Feb 21, 2024
  383. DrahtBot removed review request from hernanmarino on Feb 21, 2024
  384. DrahtBot requested review from hernanmarino on Feb 21, 2024
  385. DrahtBot requested review from w0xlt on Feb 21, 2024
  386. DrahtBot removed review request from hernanmarino on Feb 21, 2024
  387. DrahtBot removed review request from w0xlt on Feb 21, 2024
  388. DrahtBot requested review from w0xlt on Feb 21, 2024
  389. DrahtBot requested review from hernanmarino on Feb 21, 2024
  390. ryanofsky force-pushed on Feb 21, 2024
  391. ryanofsky commented at 7:18 pm on February 21, 2024: contributor
    Rebased f822ac9a24d684937f1258da89812e99c4b205ba -> 4ec6b060a80045049adc53b4db0b0837ba169cfc (pr/bresult2.44 -> pr/bresult2.45, compare) due to silent conflict with #27877
  392. DrahtBot removed the label CI failed on Feb 21, 2024
  393. ryanofsky force-pushed on Feb 21, 2024
  394. ryanofsky commented at 10:56 pm on February 21, 2024: contributor
    Updated 4ec6b060a80045049adc53b4db0b0837ba169cfc -> 20556598140030237861d21a61f646252002ddff (pr/bresult2.45 -> pr/bresult2.46, compare) making a few changes to improve compatibility with #26022
  395. in src/node/chainstate.cpp:241 in 0d33bcbf01 outdated
    241         }
    242     } else {
    243-        return {ChainstateLoadStatus::FAILURE, _(
    244-           "UTXO snapshot failed to validate. "
    245+        return util::Error{
    246+           _("UTXO snapshot failed to validate. "
    


    TheCharlatan commented at 9:30 am on February 22, 2024:
    If I’m reading this right, not specifying a failure value will lead to it being default constructed, so this will have a value of ChainstateLoadError::FAILURE. Could omitting a failure value be a compile-time error if the failure type is non-void?

    ryanofsky commented at 11:14 am on February 22, 2024:

    re: #25665 (review)

    Could omitting a failure value be a compile-time error if the failure type is non-void?

    Nice catch. That’s an interesting idea but it seems extreme because it would make it difficult to call a failure value’s default constructor, and impossible to call it the class is not copyable or movable.

    I think in general if you don’t want a failure value to be default-constructed you should pass a failure type that doesn’t have a default constructor. But we could add a check for specifically for enums. The following seems to work:

    0--- a/src/util/result.h
    1+++ b/src/util/result.h
    2@@ -136,6 +136,7 @@ protected:
    3     static void ConstructResult(Result& result, Args&&... args)
    4     {
    5         if constexpr (Failure) {
    6+            static_assert(sizeof...(args) > 0 || !std::is_enum_v<F>, "Refusing to default-construct enum failure value, please specify explicit value");
    7             result.Info().failure.emplace(std::forward<Args>(args)...);
    8         } else {
    9             result.ConstructValue(std::forward<Args>(args)...);
    

    ryanofsky commented at 11:47 am on February 22, 2024:

    re: #25665 (review)

    But we could add a check for specifically for enums.

    Latest push adds a slightly more general check that doesn’t allow any scalar failure value (int, float, enum, or pointer) to be default constructed. Could adjust this condition or make it an option if default-constructing these types of values seems useful in the future.

    This check is arguably too heavy handed because it is enforcing that the result.GetFailure() value is not default-constructed even though it technically has a default constructor. Even without the check, the Result object would be still constructed in a failure state and if (result) and if (result.has_value()) would be false. But it seems safest to start off requiring explicit scalar failure values in case they were omitted by accident.


    TheCharlatan commented at 1:05 pm on February 22, 2024:

    Nice, the compile-time check can be tested with:

     0diff --git a/src/test/result_tests.cpp b/src/test/result_tests.cpp
     1index 261e1f0b9b..053b45fceb 100644
     2--- a/src/test/result_tests.cpp
     3+++ b/src/test/result_tests.cpp
     4@@ -28,0 +29,10 @@ BOOST_AUTO_TEST_SUITE(result_tests)
     5+enum class DummyError {
     6+    FAILURE_1,
     7+    FAILURE_2,
     8+    FAILURE_3,
     9+};
    10+
    11+util::Result<void, DummyError> VoidDummyError() {
    12+    return util::Error{Untranslated("void fail.")};
    13+}
    14+
    15@@ -203,0 +214,3 @@ BOOST_AUTO_TEST_CASE(check_returned)
    16+
    17+    auto res{VoidDummyError()};
    18+    BOOST_CHECK(res.GetFailure() == DummyError::FAILURE_1);
    
  396. ryanofsky force-pushed on Feb 22, 2024
  397. ryanofsky commented at 11:54 am on February 22, 2024: contributor
    Updated 20556598140030237861d21a61f646252002ddff -> cdf7bb17563b92e48b0576a0975c168619c5aa34 (pr/bresult2.46 -> pr/bresult2.47, compare) adding a check requiring scalar failure values to be specified explicitly (https://github.com/bitcoin/bitcoin/pull/25665#discussion_r1498934743)
  398. TheCharlatan approved
  399. TheCharlatan commented at 1:20 pm on February 22, 2024: contributor
    Re-ACK cdf7bb17563b92e48b0576a0975c168619c5aa34
  400. DrahtBot removed review request from w0xlt on Feb 22, 2024
  401. DrahtBot removed review request from hernanmarino on Feb 22, 2024
  402. DrahtBot requested review from w0xlt on Feb 22, 2024
  403. DrahtBot requested review from hernanmarino on Feb 22, 2024
  404. DrahtBot added the label CI failed on Feb 23, 2024
  405. DrahtBot removed the label CI failed on Feb 28, 2024
  406. ryanofsky commented at 10:02 pm on March 22, 2024: contributor
    Converting to draft. Working on #29700 and #29642 made me want to implement more improvements to the Result class (308e38e94fcac5aedf8ed1247a096c0d271fa666 if curious)
  407. ryanofsky marked this as a draft on Mar 22, 2024
  408. ryanofsky force-pushed on Mar 26, 2024
  409. ryanofsky commented at 7:23 pm on March 26, 2024: contributor

    Updated cdf7bb17563b92e48b0576a0975c168619c5aa34 -> efb463788f8be12bcf2bacfbf99cd2308fb54c9e (pr/bresult2.47 -> pr/bresult2.48, compare) with improvements for #29700 and #29642.

    The changes are:

    • The Result::Set() method is renamed Result::Update() and now has ability to merge success and failure values from different results together instead of just replacing them. This is used in #29700 to bubble up AbortFailure values indicating whether or not AbortNode calls happened as part of failures.
    • The result class now takes a generic MessagesType parameter and isn’t hardcoded to use vector<blingual_str>. This gets rid of some complexity in the implementation and lets it handle SuccessType FailureType and MessagesType fields in a more consistent way.
  410. ryanofsky force-pushed on Mar 26, 2024
  411. DrahtBot added the label CI failed on Mar 26, 2024
  412. DrahtBot commented at 8:30 pm on March 26, 2024: contributor

    🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the documentation.

    Possibly this is due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    Leave a comment here, if you need help tracking down a confusing failure.

    Debug: https://github.com/bitcoin/bitcoin/runs/23120122503

  413. ryanofsky force-pushed on Mar 26, 2024
  414. ryanofsky force-pushed on Mar 27, 2024
  415. DrahtBot removed the label CI failed on Mar 27, 2024
  416. ryanofsky marked this as ready for review on Mar 27, 2024
  417. ryanofsky force-pushed on Mar 28, 2024
  418. in src/util/result.h:342 in 9fe6a68170 outdated
    343         if constexpr (DstConstructed) {
    344-            if (dst) {
    345+            if (dst && src) {
    346+                // dst and src both hold success values, so merge them and return
    347+                if constexpr (!std::is_same_v<typename SrcResult::SuccessType, void>) {
    348+                    ResultTraits<typename SrcResult::SuccessType>::MergeInto(*dst, *src);
    


    TheCharlatan commented at 9:17 pm on April 4, 2024:
    Reviewers might be wondering what the benefits of the ResultTraits are. They are useful for potential future extensions of the move behaviour: https://github.com/bitcoin/bitcoin/pull/29700/commits/3951afc3b708326cea653951ef331d8f96a28682 .

    ryanofsky commented at 2:00 pm on April 18, 2024:

    re: #25665 (review)

    Reviewers might be wondering what the benefits of the ResultTraits are. They are useful for potential future extensions of the move behaviour: 3951afc .

    Thanks I added a comment to the traits struct to make this clearer. Traits are an old pattern in c++ and are commonly used. The standard library has std::allocator_traits, std::iterator_traits, std::pointer_traits, std::hash, std::formatter, std::tuple_element, and std::tuple_size trait structs which allow generic library functions to work with user defined types. They all work the same way, by defining a template struct in the library that user code can specialize to control the behavior of library functions when called with custom types. This is an old explanation of traits which may be helpful: http://erdani.org/publications/traits.html

  419. in src/util/result.h:109 in 9fe6a68170 outdated
    114+    static void MergeInto(Messages& dst, Messages& src);
    115+};
    116+
    117+//! MessagesTraits specialization for Messages struct.
    118+template<>
    119+struct MessagesTraits<Messages> {
    


    TheCharlatan commented at 9:30 pm on April 4, 2024:
    I have not quite grasped yet what the immediate benefit of these more generic decorators on the Messages are. Am I missing something from your draft PRs? I like shiny generics though, so to me a clear benefit of this approach would be to future-proof the result type. I was asking myself if this could be a bit more readable if instead of a forward-declared struct, the MessagesTraits were made a concept. After implementing it, I think it is indeed a bit easier to parse at the call site. It also potentially allows the user to define their own types for the warnings and errors fields like this: https://github.com/TheCharlatan/bitcoin/commit/f5e5442cf80c58c0509562d8c689243c8d9becf2 . Could this also be leveraged in place of your future addition of an info type?

    ryanofsky commented at 1:58 pm on April 18, 2024:

    re: #25665 (review)

    I have not quite grasped yet what the immediate benefit of these more generic decorators on the Messages are.

    The benefit is letting the Result struct work with any possible MessagesType without placing requirements on it. The PR provides a simple default type to hold error and warning messages:

    0struct Messages {
    1    std::vector<bilingual_str> errors{};
    2    std::vector<bilingual_str> warnings{};
    3};
    

    The type was defined this way because we currently have wallet code which returns lists of errors and warnings and wallet RPCs which return them in separate arrays. But I don’t want to hardcode the Result type to use this particular struct, because it shouldn’t care about MessagesType internals and because other representations of errors and warnings are reasonable too. Maybe a representation that preserved the relative order of errors and warnings instead of keeping them in separate arrays would be good. Maybe a representation that discarded translated strings, or only held errors without allowing warnings, or used a different data structure would be good. The MessagesTraits class lets the Result class not care about these things and work with any messages type.

    I was asking myself if this could be a bit more readable if instead of a forward-declared struct, the MessagesTraits were made a concept. After implementing it, I think it is indeed a bit easier to parse at the call site. It also potentially allows the user to define their own types for the warnings and errors fields like this: TheCharlatan@f5e5442 . Could this also be leveraged in place of your future addition of an info type?

    That is interesting to see but I’m not sure it is more readable if you are familiar with the traits pattern. Another drawback is it requires every MessagesType to be a struct that has AddError, AddWarning, HasMessages, ErrorType, and WarningType members, which is not the case right now. Right now MessagesType could be any type including the simple Messages struct above, a plain string, even something like a bool or a custom type without public members. The suggestion is also +57 -36 lines excluding tests, so it doesn’t really seem like a simplification, and the more complicated Messages struct definition also does not seem good to expose.

  420. ryanofsky force-pushed on Apr 18, 2024
  421. ryanofsky commented at 4:24 pm on April 18, 2024: contributor
    Updated 7a4741eaf892646e9d02e440c39fbbfa03f29fc3 -> 28e20812109757e307bc29a4adbef8aae90d94a6 (pr/bresult2.52 -> pr/bresult2.53, compare) just improving some comments
  422. ryanofsky marked this as a draft on Apr 18, 2024
  423. ryanofsky commented at 7:13 pm on April 18, 2024: contributor

    Converted to draft since first 2 commits were moved to a separate PR, #29906


    Rebased 28e20812109757e307bc29a4adbef8aae90d94a6 -> 990f9d65c5e15ab26c341c21829a697f5cddfa6c (pr/bresult2.53 -> pr/bresult2.54, compare) on top of updated base PR #29906, also adding more comments, simplifying code a little bit by util::MoveFrom helper, and using Update() method more places for consistency.

  424. ryanofsky force-pushed on Apr 24, 2024
  425. DrahtBot added the label CI failed on Apr 25, 2024
  426. ryanofsky force-pushed on Apr 26, 2024
  427. DrahtBot removed the label CI failed on Apr 26, 2024
  428. ryanofsky force-pushed on Apr 26, 2024
  429. stickies-v commented at 1:22 pm on April 26, 2024: contributor

    Posting my response to part of [your comment on #29906](/bitcoin-bitcoin/29906/#issuecomment-2078452007) here, since Update() is no longer relevant to that PR:

    Examples would be the BlockManager::SaveBlockToDisk function from #29700

    Had just a quick look, but this looks like how I’d expect Update() to be used, indeed.

    or the CompleteChainstateInitialization function from #25665

    I don’t think this is good usage of Update(), since there is no chaining happening here. I would find list initialization more readable and less error-prone:

      0diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
      1index 0671f75515..8cc43c53e5 100644
      2--- a/src/node/chainstate.cpp
      3+++ b/src/node/chainstate.cpp
      4@@ -40,7 +40,6 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
      5     const CacheSizes& cache_sizes,
      6     const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
      7 {
      8-    util::Result<InterruptResult, ChainstateLoadError> result;
      9     auto& pblocktree{chainman.m_blockman.m_block_tree_db};
     10     // new BlockTreeDB tries to delete the existing file, which
     11     // fails if it's still open from the previous loop. Close it first:
     12@@ -61,8 +60,7 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     13     }
     14 
     15     if (chainman.m_interrupt) {
     16-        result.Update(Interrupted{});
     17-        return result;
     18+        return Interrupted{};
     19     }
     20 
     21     // LoadBlockIndex will load m_have_pruned if we've ever removed a
     22@@ -71,26 +69,23 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     23     // From here on, fReindex and options.reindex values may be different!
     24     if (!chainman.LoadBlockIndex()) {
     25         if (chainman.m_interrupt) {
     26-            result.Update(Interrupted{});
     27+            return Interrupted{};
     28         } else {
     29-            result.Update({util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE});
     30+            return {util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE};
     31         }
     32-        return result;
     33     }
     34 
     35     if (!chainman.BlockIndex().empty() &&
     36             !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) {
     37         // If the loaded chain has a wrong genesis, bail out immediately
     38         // (we're likely using a testnet datadir, or the other way around).
     39-        result.Update({util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
     40-        return result;
     41+        return {util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
     42     }
     43 
     44     // Check for changed -prune state.  What we are concerned about is a user who has pruned blocks
     45     // in the past, but is now trying to run unpruned.
     46     if (chainman.m_blockman.m_have_pruned && !options.prune) {
     47-        result.Update({util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE});
     48-        return result;
     49+        return {util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE};
     50     }
     51 
     52     // At this point blocktree args are consistent with what's on disk.
     53@@ -98,8 +93,7 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     54     // (otherwise we use the one already on disk).
     55     // This is called again in ImportBlocks after the reindex completes.
     56     if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
     57-        result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
     58-        return result;
     59+        return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE};
     60     }
     61 
     62     auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
     63@@ -133,17 +127,15 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     64         // Refuse to load unsupported database format.
     65         // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
     66         if (chainstate->CoinsDB().NeedsUpgrade()) {
     67-            result.Update({util::Error{_("Unsupported chainstate database format found. "
     68+            return {util::Error{_("Unsupported chainstate database format found. "
     69                                          "Please restart with -reindex-chainstate. This will "
     70                                          "rebuild the chainstate database.")},
     71-                                       ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
     72-            return result;
     73+                                       ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
     74         }
     75 
     76         // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
     77         if (!chainstate->ReplayBlocks()) {
     78-            result.Update({util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE});
     79-            return result;
     80+            return {util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE};
     81         }
     82 
     83         // The on-disk coinsdb is now in a good state, create the cache
     84@@ -153,8 +145,7 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     85         if (!is_coinsview_empty(chainstate)) {
     86             // LoadChainTip initializes the chain based on CoinsTip()'s best block
     87             if (!chainstate->LoadChainTip()) {
     88-                result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
     89-                return result;
     90+                return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE};
     91             }
     92             assert(chainstate->m_chain.Tip() != nullptr);
     93         }
     94@@ -164,9 +155,8 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
     95         auto chainstates{chainman.GetAll()};
     96         if (std::any_of(chainstates.begin(), chainstates.end(),
     97                         [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
     98-            result.Update({util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
     99-                                                 chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE});
    100-            return result;
    101+            return {util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
    102+                                                 chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE};
    103         };
    104     }
    105 
    106@@ -175,7 +165,7 @@ static util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInit
    107     // on the condition of each chainstate.
    108     chainman.MaybeRebalanceCaches();
    109 
    110-    return result;
    111+    return {};
    112 }
    113 
    114 util::Result<InterruptResult, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
    
  430. ryanofsky commented at 3:27 pm on April 26, 2024: contributor

    re: #25665 (comment)

    or the CompleteChainstateInitialization function from #25665

    I don’t think this is good usage of Update(), since there is no chaining happening here. I would find list initialization more readable and less error-prone:

    git diff on 1376583

    The diff you suggested is actually the way this function was implemented before #29700. The problem is that #29700 updates some validation functions to return util::Result instead of bool. So instead of CompleteChainstateInitialization being a function that just returns a util::Result value, it becomes a function that returns a util::Result value and also calls other functions returning util::Result. Which is the scenario where the result.Update() pattern is useful, because it allows returning warning and error messages from functions being called instead of discarding them, so more complete error information is returned by default when a problem happens.

    If you think this pattern is not readable enough or too error prone, we could probably come up with an alternate pattern that doesn’t require updating a variable (but may require copying around data more manually). In general, I’d agree that all other things being equal, it’s better to initialize variables once than change them over time. But in this case, if the goal is to accumulate error and warning messages, I think a pattern where you declare a single variable at the top of the function holding the messages, and update the variable over the course of the function, and then return the variable at the end is a pretty simple and readable pattern. I’m sure other patterns could work too, though.

    I guess I’d want to know if there’s another pattern you’d prefer, or what more specific problems you see with the current code.

  431. DrahtBot added the label CI failed on Apr 26, 2024
  432. stickies-v commented at 2:57 pm on April 27, 2024: contributor

    The problem is that #29700 updates some validation functions to return util::Result instead of bool.

    I see, that was not apparent in the link you shared earlier. Would it make sense to do that refactoring in #29700 then, instead of in this PR? Because in this PR, I don’t think the change makes a lot of sense on its own.

    I think a pattern where you declare a single variable at the top of the function holding the messages, and update the variable over the course of the function, and then return the variable at the end is a pretty simple and readable pattern.

    I agree, I think that’s a reasonable pattern. For this situation, I would prefer using two functions: Update() when chaining is happening, and Set(), Replace(), operator=(), … when we expect Result to be empty. This way, we can more easily identify potential bugs when e.g. Set() is used but the Result has already had a value assigned to it earlier. Potentially, could even do a run-time assertion (I don’t think compile time is possible?).

    Combining both would be suitable in this case, I think. Pseudo-code diff (since Set() isn’t implemented) where we only keep Update() for the chaining bits:

     0diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
     1index 014f6915ad..bc8d547153 100644
     2--- a/src/node/chainstate.cpp
     3+++ b/src/node/chainstate.cpp
     4@@ -64,7 +64,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
     5     }
     6 
     7     if (chainman.m_interrupt) {
     8-        result.Update(Interrupted{});
     9+        result.Set(Interrupted{});
    10         return result;
    11     }
    12 
    13@@ -85,14 +85,14 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    14             !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) {
    15         // If the loaded chain has a wrong genesis, bail out immediately
    16         // (we're likely using a testnet datadir, or the other way around).
    17-        result.Update({util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
    18+        result.Set({util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
    19         return result;
    20     }
    21 
    22     // Check for changed -prune state.  What we are concerned about is a user who has pruned blocks
    23     // in the past, but is now trying to run unpruned.
    24     if (chainman.m_blockman.m_have_pruned && !options.prune) {
    25-        result.Update({util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE});
    26+        result.Set({util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE});
    27         return result;
    28     }
    29 
    30@@ -101,7 +101,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    31     // (otherwise we use the one already on disk).
    32     // This is called again in ImportBlocks after the reindex completes.
    33     if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
    34-        result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
    35+        result.Set({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
    36         return result;
    37     }
    38 
    39@@ -136,7 +136,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    40         // Refuse to load unsupported database format.
    41         // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
    42         if (chainstate->CoinsDB().NeedsUpgrade()) {
    43-            result.Update({util::Error{_("Unsupported chainstate database format found. "
    44+            result.Set({util::Error{_("Unsupported chainstate database format found. "
    45                                          "Please restart with -reindex-chainstate. This will "
    46                                          "rebuild the chainstate database.")},
    47                                        ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
    48@@ -145,7 +145,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    49 
    50         // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
    51         if (!chainstate->ReplayBlocks()) {
    52-            result.Update({util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE});
    53+            result.Set({util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE});
    54             return result;
    55         }
    56 
    57@@ -156,7 +156,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    58         if (!is_coinsview_empty(chainstate)) {
    59             // LoadChainTip initializes the chain based on CoinsTip()'s best block
    60             if (!chainstate->LoadChainTip()) {
    61-                result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
    62+                result.Set({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
    63                 return result;
    64             }
    65             assert(chainstate->m_chain.Tip() != nullptr);
    66@@ -167,7 +167,7 @@ static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateIniti
    67         auto chainstates{chainman.GetAll()};
    68         if (std::any_of(chainstates.begin(), chainstates.end(),
    69                         [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
    70-            result.Update({util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
    71+            result.Set({util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
    72                                                  chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE});
    73             return result;
    74         };
    

    It’s kind of a similar situation to having a std::vector<>&out-parameter that we use here and there in the codebase. When that happens, first you need to check if the vector is supposed to be empty or not. Then, you need to check if it actually is empty. Sometimes we call .clear() in the beginning of the function, sometimes we don’t. It’s just confusing and time consuming and error prone (see e.g. #26289). With differentiated Update() and Set() functions we allow the developer to signal intent, which makes it easier to understand the code and to identify potential bugs (manually or through run-time/fuzzing checks).

    I’ve ranted about this already a bit too long, so please do let me know if you (dis)agree but I’ll stop commenting further until I’ve (re)-reviewed the rest of this PR and I maybe get new insights.

  433. achow101 referenced this in commit 2d3056751b on Apr 30, 2024
  434. DrahtBot added the label Needs rebase on Apr 30, 2024
  435. ryanofsky force-pushed on May 1, 2024
  436. ryanofsky marked this as ready for review on May 1, 2024
  437. ryanofsky commented at 5:46 pm on May 1, 2024: contributor
    Rebased 0c8a1bb1445e8b88bb0ad9d440830ef215e9e8f8 -> db91dbb5a9a0413d6ee22ed6e32d1221d5b6d996 (pr/bresult2.56 -> pr/bresult2.57, compare) after #29906 was merged
  438. ryanofsky commented at 7:26 pm on May 1, 2024: contributor

    re: #25665 (comment)

    Thanks @stickies-v. I think since Update() is not required immediately I might move the first and third commits of this PR to another PR without Update(). The only downside will be some churn in the CompleteChainstateInitialization function, since I believe at some point it will need to be changed again to use the Update() pattern or a similar pattern, when other functions it calls are changed to return util::Result.

    On your latest feedback, I think I understand the scenario you are concerned about. It seems like you are worried about future uses of util::Result where sometimes completely resetting a result object is useful, and other times updating pieces of the result object is useful. And you want developers to clearly indicate their intent in both cases by calling Set() in the former case and Update() in the latter case. If developers were required to express their intent this way, it could avoid bugs when existing fields of a result object are unintentionally cleared by a Set() call, or unintentionaly left with stale values after an Update() call.

    I think I might have agreed with that before writing #25722 and #29700. But after writing these PRs, I don’t think a Set method would be useful in practice. The most straightforward way to write functions that return util::Result and also pass along error messages from other functions returning util::Result is to use result.Update(). The most straightforward way to write functions that just return util::Result without forwarding messages from other functions is just to construct separate result objects and not mutate existing objects. I didn’t encounter scenarios where code would be clearer or safer if it reset an existing object instead just declaring a new object, or just updating the relevant fields in an existing object.

    Basically, I think the result.Update() pattern is a safe, simple pattern to follow that was general enough to handle the cases I encountered in #25722 and #29700 without the need the need for set, reset, or assignment. If that statement is not convincing, I understand, and think we could move forward with a smaller PR that does not include Update(). You might also be interested to look at a random commit from #25722 and #29700 and see if there’s another way you think some of that code should be written. A lot of the code in #25722 especially was not using result.Update() pattern initially and was just using separate result objects for everything.

  439. DrahtBot removed the label Needs rebase on May 1, 2024
  440. DrahtBot removed the label CI failed on May 1, 2024
  441. DrahtBot added the label Needs rebase on May 20, 2024
  442. refactor: Add util::Result failure values
    Add util::Result support for returning more error information. For better error
    handling, adds the ability to return a value on failure, not just a value on
    success. This is a key missing feature that made the result class not useful
    for functions like LoadChainstate() which produce different errors that need to
    be handled differently.
    
    This change also makes some more minor improvements:
    
    - Smaller type size. util::Result<int> is 16 bytes, and util::Result<void> is 8
      bytes. Previously util::Result<int> and util::Result<void> were 72 bytes.
    
    - More documentation and tests.
    41f6b999d0
  443. refactor: Add util::Result::Update() method
    Add util::Result Update method to make it possible to change the value of an
    existing Result object. This will be useful for functions that can return
    multiple error and warning messages, and want to be able to change the result
    value while keeping existing messages.
    6fff420672
  444. refactor: Use util::Result class in LoadChainstate and VerifyLoadedChainstate 79a970d4f1
  445. refactor: Add util::Result multiple error and warning messages
    Add util::Result support for returning warning messages and multiple errors,
    not just a single error string. This provides a way for functions to report
    errors and warnings in a standard way, and simplifies interfaces.
    
    The functionality is unit tested here, and put to use in followup PR
    https://github.com/bitcoin/bitcoin/pull/25722
    ba959dbed3
  446. test: add static test for util::Result memory usage
    Suggested by Martin Leitner-Ankerl <martin.ankerl@gmail.com>
    https://github.com/bitcoin/bitcoin/pull/25722#discussion_r1174298529
    
    Co-authored-by: Martin Leitner-Ankerl <martin.ankerl@gmail.com>
    b08548336e
  447. ryanofsky force-pushed on Jun 17, 2024
  448. DrahtBot removed the label Needs rebase on Jun 17, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-01 13:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me