-
Notifications
You must be signed in to change notification settings - Fork 795
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
Error handling using std::error_code and the proposed std::expected type #801
Conversation
f0f793a
to
f8eab00
Compare
f8eab00
to
616da17
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more general thoughts:
- It looks like you didn't transform some RPC functions, like
block_create
. Was there a reason for this? - Should we return an error code from the RPC functions?
- Should we use something similar to the Rust try macro (now an operator) for preconditions, and maybe other places?
- When
std::expected
is converted to a boolean, it returns true if it was successful. There's not much we can do about this, but it is the opposite of our previous idiom of errors being truthy. - Should we keep the success-first pattern in if statements? Personally I favor putting the error first so it's right next to the failing condition.
Overall I really like the direction of this PR.
rai/node/rpc.cpp
Outdated
std::string account_text; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: accidental whitespace change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, fixed.
rai::block_hash hash; | ||
auto head_str (request.get_optional<std::string> ("head")); | ||
rai::transaction transaction (node.store.environment, nullptr, false); | ||
if (head_str) | ||
{ | ||
error = hash.decode_hex (*head_str); | ||
if (!error) | ||
if (hash.decode_hex (*head_str)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this condition should be inverted if we're going with the success-first pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my main comment about this topic.
if (destinations_text.is_initialized ()) | ||
|
||
auto destinations (decode_unsigned (request.get<std::string> ("destinations", "0"))); | ||
if (!ec && !destinations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: in an above function (I don't remember which) you don't check ec
when parsing future values. It doesn't matter that much, but we should probably be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, though I don't seem to find the missing check.
Yeah, only a few functions are converted as I wanted to set up a path for using std::expected and get that reviewed. There's a lot of actions to be converted and tested properly. Serg offered to help with the conversion once the framework is in place. If you prefer this PR to do the full conversion of RPC instead of multiple PR's, I'm happy to do that, though it'll take some time for me to get around to that.
I was thinking that One option would be to let process_request do the error response, but doing it in the action handler allows for some useful idioms, see end of
That would be great. P0779R0 is a proposal to implement this, which will work with std::expected. Not sure what a good C++ idiom is right now. There's also a monadic extension proposal to allow chaining of expected.
Since std::expected is not an error, but a value or an error, I think it makes sense. I see your point, though - it's going to be a bit painful to read the code base as the old idiom gets slowly replaced.
I have no strong opinions here. Possibly the no-early-returns principle in the code base affected the decision to place errors at the end, since you're then forced to nest statements ? |
034269a
to
334b243
Compare
rai/node/rpc.cpp
Outdated
boost::optional<std::string> modified_since_text (request.get_optional<std::string> ("modified_since")); | ||
if (modified_since_text.is_initialized ()) | ||
auto count (decode_unsigned (request.get<std::string> ("count", ""), std::numeric_limits<uint64_t>::max ())); | ||
if (!count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the condition that should have a !ec
if we're going with that style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch, I'll add that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
Should we make our own macro for the time being? |
The proposal requires language changes. A decent C-style macro solution would require statement expressions, which is not supported by MSVC. Unless someone has come up with an alternative trick that I'm not aware of. Do you have any ideas on how to solve this properly on C++14? |
Oh, I forgot that statements aren't expressions in C++ (unlike in Rust 😉). We could probably make a macro that takes the name of a variable and sets the value, but that doesn't seem like a good idea. |
2c2825e
to
9f8e10b
Compare
9f8e10b
to
3d45038
Compare
I need std::expected/std::error_code in the IPC/API code now so I'll introduce it there. The RPC changes can be rebased on top of that later. |
Docs and rationale: https://github.com/cryptocode/raiblocks/wiki/Error-handling-in-the-node-using-std::error_code-and-std::expected
This PR applies the above error handling ideas to a handful of RPC handlers. A few functions with the double-response-write problem are fixed as well, using std::error_code.
I've put a few error codes in a common enum, which can be used elsewhere later on.
decode_unsigned
is used to teststd::expected
which allows both values and errors to be returned. I thinkexpected
is a good fit for the node, but it's probably a good idea to introduce it gradually to see how well things compose.Since expected isn't standardized yet (expected in C++20), I've included
expected-light
(a single-header file) which we'll remove once compilers ships with a std implementation.