Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a try_unwrap(...) or_return construct, similar to Rust's ? operator #6554

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

daira
Copy link
Contributor

@daira daira commented Apr 14, 2023

After #include "util/unwrap.h",

try_unwrap(x) or_return is roughly equivalent to (using the GCC/clang statement expression extension):

({
    auto m = (x);
    if (!m.has_value()) return adapt_error(m);
    m.value(); // if m has a non-void value_type
})

where adapt_error(m) preserves the error (or null) status of m but may adapt it to the correct value type.

try_unwrap(x) or_return_unexpected(err) is roughly equivalent to:

({
    auto m = (x);
    if (!m.has_value()) return tl::make_unexpected(err);
    m.value(); // if m has a non-void value_type
})

try_unwrap(x) or_assert is roughly equivalent to:

({
    auto m = (x);
    assert(m.has_value());
    m.value(); // if m has a non-void value_type
})

These constructs work correctly for any type T that has:

  • a value_type member;
  • a bool has_value() method;
  • a T::value_type value() method that can be called when has_value() returns true. This need not exist if T::value_type is void.

These conditions are met for tl::expected<T, E>, the proposed std::expected<T, E> [*], std::optional<T>, and potentially your custom type.

See the tests in src/gtest/test_unwrap.cpp for what is possible in terms of conversion between types.

[*] Caveats: type adaption is not enabled for std::expected (but could easily be added), and try_unwrap(x) or_return_unexpected(err) will always return tl::expected in the error case.

fixes #6552

@daira daira force-pushed the unwrap_or_return branch 2 times, most recently from 1375714 to 0e4b5d3 Compare April 23, 2023 02:10
@daira daira added I-error-handling Problems and improvements related to error handling safe-to-build Used to send PR to prod CI environment labels Apr 23, 2023
@daira daira marked this pull request as ready for review April 23, 2023 02:12
@daira daira changed the title Add an unwrap { ... } or_return construct, similar to Rust's ? operator Add an try_unwrap { ... } or_return construct, similar to Rust's ? operator Apr 23, 2023
@daira daira changed the title Add an try_unwrap { ... } or_return construct, similar to Rust's ? operator Add an try_unwrap(...) or_return construct, similar to Rust's ? operator Apr 23, 2023
@daira daira changed the title Add an try_unwrap(...) or_return construct, similar to Rust's ? operator Add a try_unwrap(...) or_return construct, similar to Rust's ? operator Apr 23, 2023
maxSaplingAvailable,
maxOrchardAvailable,
orchardOutputs)
) or_return;
Copy link
Contributor Author

@daira daira Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that the map_errors (two of them in this function) go away because ResolvePayment returns a tl::expected<ResolvedPayment, AddressResolutionError>, and AddressResolutionError is a variant of InputSelectionError. So this just works, because there is an implicit conversion from each arm of a variant to the variant type.

Copy link
Contributor Author

@daira daira Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not wedded to the convention I've adopted here of putting the ) or return at the start of the line, but it does make it easier to see that there is a potential return. I looked at whether it was possible to get Visual Studio to highlight these as keywords, but couldn't find the highlighting config file for C++.

});
});
try_unwrap(ValidateAmount(spendable, initialFee)
) or_return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just works with void types (this was the most complicated part).

@ECC-CI ECC-CI removed the safe-to-build Used to send PR to prod CI environment label Apr 23, 2023
orchardOutputs)
) or_return;

return {std::make_pair(actualPayment, finalFee)};
Copy link
Contributor Author

@daira daira Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of braces here isn't essential; just a matter of style because we're constructing a tl::expected from this pair (as we were before).

});
try_unwrap(ValidateAmount(spendable, initialFee)
) or_return;
const ResolvedPayment rpayment = try_unwrap(ResolvePayment(
Copy link
Contributor Author

@daira daira Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const auto rpayment would also work here; the try_unwrap doesn't interfere with type inference. I just prefer it this way. (You do sometimes have to add explicit return types to lambdas when refactoring to use try_unwrap.)

int v = try_unwrap(ex) or_assert;
return {v+1};
};
ASSERT_DEATH(fn(err), "has_value()");
Copy link
Contributor Author

@daira daira Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain these death tests will work on all platforms (in particular, I don't know if Windows will preserve the assertion text that we check for here). The test assumes NDEBUG is not defined, which is always the case for zcashd.

@daira daira added the safe-to-build Used to send PR to prod CI environment label Apr 23, 2023
@ECC-CI ECC-CI removed the safe-to-build Used to send PR to prod CI environment label Apr 23, 2023
@daira daira added the safe-to-build Used to send PR to prod CI environment label Apr 23, 2023
@ECC-CI ECC-CI removed the safe-to-build Used to send PR to prod CI environment label Apr 23, 2023
src/util/unwrap.h Outdated Show resolved Hide resolved
src/util/unwrap.h Outdated Show resolved Hide resolved
src/util/unwrap.h Outdated Show resolved Hide resolved
`try_unwrap(x) or_return` is roughly equivalent to (using the GCC/clang
statement expression extension):
```c++
({
    auto m = (x);
    if (!m.has_value()) return adapt_error(m);
    m.value(); // if m has a non-void value_type
})
```
where `adapt_error(m)` preserves the error (or null) status of `m` but
may adapt it to the correct value type.

`try_unwrap(x) or_return_unexpected(err)` is roughly equivalent to:
```c++
({
    auto m = (x);
    if (!m.has_value()) return tl::make_unexpected(err);
    m.value(); // if m has a non-void value_type
})
```

`try_unwrap(x) or_assert` is roughly equivalent to:
```c++
({
    auto m = (x);
    assert(m.has_value());
    m.value(); // if m has a non-void value_type
})
```

These constructs work correctly for any type T that has:
 * a `value_type` member;
 * a `bool has_value()` method;
 * a `T::value_type value()` method that can be called when `has_value()`
   returns true. This need not exist if `T::value_type` is void.

These conditions are met for `tl::expected<T, E>`, the proposed
`std::expected<T, E>`, `std::optional<T>`, and potentially your custom
type.

See the tests in `src/gtest/test_unwrap.cpp` for what is possible in
terms of conversion between types.

Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
@daira daira added the safe-to-build Used to send PR to prod CI environment label Oct 2, 2023
@daira
Copy link
Contributor Author

daira commented Oct 2, 2023

I suggest reviewing with "Hide whitespace" initially. (One of the points of this is to avoid the tab slide to the right caused by nested error handling.)

@daira daira removed the request for review from sellout October 2, 2023 16:24
@daira daira added this to the Release 5.8.0 milestone Oct 2, 2023
@ECC-CI ECC-CI removed the safe-to-build Used to send PR to prod CI environment label Oct 2, 2023
@str4d str4d modified the milestones: Release 5.8.0, Post 5.8.0 Dec 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-error-handling Problems and improvements related to error handling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider a macro corresponding to Rust's ? operator
3 participants