util: generalize util::Result to support custom errors #34005

pull l0rinc wants to merge 1 commits into bitcoin:master from l0rinc:l0rinc/generalized-Result-error changing 2 files +53 −12
  1. l0rinc commented at 2:06 pm on December 4, 2025: contributor

    Context

    While reviewing #33657 it became clear we don’t have a good value-or-error wrapper, similar in spirit to std::expected<T, E> in C++23.

    Problem

    The util::Result helper currently stores a std::variant<bilingual_str, T> and is effectively only usable for high-level code that needs to propagate user-facing error strings. Low-level code that wants typed error codes instead of strings still has to expose raw std::variant (or roll custom structs).

    Fix

    Generalize util::Result to take a second template parameter E for the error type, and store std::variant<E, T> internally. The default E remains bilingual_str, so existing uses of Result<T> and util::Error{...} behave exactly as before and ErrorString(Result<T>) continues to work.

    New code can now use typed errors and avoid the awkward std::variant getter, e.g.:

    0if (auto ret{chainman->m_blockman.ReadRawBlock(WITH_LOCK(cs_main, return index.GetBlockPos()))}) {
    1    block = std::move(*ret);
    2    return true;
    3}
    4return false;
    
  2. DrahtBot added the label Utils/log/libs on Dec 4, 2025
  3. DrahtBot commented at 2:06 pm on December 4, 2025: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34005.

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34006 (Add util::Expected (std::expected) by maflcko)
    • #26022 (Add util::ResultPtr class by ryanofsky)
    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
    • #25665 (refactor: Add util::Result failure values, multiple error and warning messages 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.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • uses continue -> continues / will continue [grammatical error: “uses continue” is incorrect; use “continues” or “will continue” for proper verb form]

    2025-12-04

  4. fanquake commented at 2:09 pm on December 4, 2025: member
    Have you looked at #25665?
  5. ryanofsky commented at 2:28 pm on December 4, 2025: contributor

    Have you looked at #25665?

    Looking at bb61eca55a563d7486df3c356d163c4af10a36c8, I think this is compatible with #25665. This PR is a minimal tweak to util::Result that can let it work as a substitute for std::expected before std::expected is available to use. #25665 could be easily rebased on top of this. In the long run, though, I think code that doesn’t need util::Result error reporting functionality should just use std::expected directly.

    It might be good if this added an alias like:

    0template<typename T, typename F>
    1using Expected = Result<T, F>;
    

    So it could be clearer which code should eventually use std::expected when it’s available, and replacement could potentially be a scripted-diff. We did something similar with Span/std::span

  6. util: generalize `util::Result` to support custom errors
    The `util::Result` helper currently wraps a `std::variant<bilingual_str, T> `and is only usable for high-level code that needs to propagate user-facing error strings.
    This change generalizes `Result` so it can also be used in low-level code paths with typed error codes instead of raw `std::variant` or `std::expected`.
    
    Instead of hard-coding `bilingual_str` in the variant, the error is now templated, so the existing uses of `Result<T>` behave as before.
    
    Call sites get an explicit value-or-error result type without having to manipulate std::variant directly.
    The design mirrors the shape of `std::expected<T, E>` in C++23, but remains usable on our current C++20 baseline, similar to how `Span` provided a precursor to `std::span`.
    d44f14075a
  7. l0rinc force-pushed on Dec 4, 2025
  8. DrahtBot added the label CI failed on Dec 4, 2025
  9. maflcko commented at 2:36 pm on December 4, 2025: member

    I wonder if this is the right approach. util::Result is mostly meant for high level code (dealing with translations), such as init, the wallet, or the gui. For low-level stuff, it could make sense to use std::expected directly?

    I am not sure how fast we’ll be getting C++23. C++20 was enabled two years ago in #28349, but there are still questions around which C++20 features are supported. (e.g. jthread is only in libc++ 20, std::format is only in g++13, …)

    So I think just porting a minimal std::expected seems easier?

    So I did that in https://github.com/bitcoin/bitcoin/pull/34006

  10. l0rinc commented at 2:38 pm on December 4, 2025: contributor

    It might be good if this added an alias like

    Good idea, added and used it in the new test cases.

    Have you looked at #25665?

    I agree with @ryanofsky that this is likely compatible with the PR and as far as I understand it addresses a small chunk of what it’s also trying to achieve.

    So I think just porting a minimal std::expected seems easier? So I did that in #34006

    I’m fine with both, let me know if I should close this to favor the other one. Not sure why we’d need two, but it’s not a big deal either way.

  11. maflcko commented at 2:43 pm on December 4, 2025: member

    Looking at bb61eca, I think this is compatible with #25665. This PR is a minimal tweak to util::Result that can let it work as a substitute for std::expected before std::expected is available to use. #25665 could be easily rebased on top of this. In the long run, though, I think code that doesn’t need util::Result error reporting functionality should just use std::expected directly.

    Not sure they are conceptually compatible. 25665 aims to reduce the error footprint by wrapping it in a unique_ptr, whereas std::expected is basically a variant wrapper (keeps the memory in the struct itself).

    So a scripted-diff going from an Expected (via Result), to std::expected, may have performance impact, if the error is large enough.

    I think it is better to use std::unique_ptr explicitly in Expected/std::expected, when needed.

  12. ryanofsky commented at 2:46 pm on December 4, 2025: contributor

    So I think just porting a minimal std::expected seems easier?

    So I did that in #34006

    Nice, that’s more complicated than this PR, but not too bad. I don’t have any particular preference between these two PR’s. Either seem fine.

  13. maflcko commented at 3:02 pm on December 4, 2025: member

    So I think just porting a minimal std::expected seems easier? So I did that in #34006

    Nice, that’s more complicated than this PR, but not too bad. I don’t have any particular preference between these two PR’s. Either seem fine.

    Yeah, it is a bit more code, but, the inner guts of util::Expected are mostly copy-pasted from the current util::Result in master, so it shouldn’t be conceptually complicated.

  14. DrahtBot removed the label CI failed on Dec 4, 2025
  15. ryanofsky commented at 7:13 pm on December 4, 2025: contributor

    re: #34005 (comment)

    Not sure they are conceptually compatible. 25665 aims to reduce the error footprint by wrapping it in a unique_ptr, whereas std::expected is basically a variant wrapper (keeps the memory in the struct itself).

    Yes, it’s true that hypothetically if this PR got merged and then #25665 got merged, performance of code using util::Expected could be affected because of unique_ptr use. And then, if there was a scripted-diff replacing util::Expected with std::expected it would switch back again.

    I don’t see that as a concern for this PR, since the current Result class could just be renamed to Expected in #25665 to avoid that if desired. So #34005 and #34006 both seem like good solutions to me, and I’d be happy with either.

  16. maflcko commented at 10:26 am on December 5, 2025: member

    I don’t see that as a concern for this PR, since the current Result class could just be renamed to Expected in #25665 to avoid that if desired. So #34005 and #34006 both seem like good solutions to me, and I’d be happy with either.

    Either is fine by me as well. It just seems more churn to (1) introduce the Expected alias based on Result, (2) move it a different file, (3) likely rework it to remove the implicit bilingual_string error stuff (because it is not needed and confusing). It seems easier to just add Expected in its intended form in one go. But no strong opinion, anything seems fine here.

  17. in src/util/result.h:104 in d44f14075a
    100-    return result ? bilingual_str{} : std::get<0>(result.m_variant);
    101+    return result ? bilingual_str{} : result.error();
    102 }
    103+
    104+template<typename T, typename F>
    105+using Expected = Result<T, F>;
    


    maflcko commented at 10:26 am on December 5, 2025:
    if this alias is added, one should also be added for std::unexpected.
  18. l0rinc commented at 12:12 pm on December 5, 2025: contributor

    It seems easier to just add Expected in its intended form in one go

    Glad you have that alternative ready, closing in favor of https://github.com/bitcoin/bitcoin/pull/34006

  19. l0rinc closed this on Dec 5, 2025


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: 2025-12-06 18:13 UTC

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