-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Make a stand-in for std::expected
available in the code base
#3916
Conversation
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.
I took a look at the wrapper and that looks great!
- The code is simple - this isn't an overly complex wrapper over outcome.
- Making this class "pointer-like" makes a lot of sense, and I do think that's a nice improvement over raw outcome.
Yeah, I think it's time to add a class like this and clean up some of the existing interfaces. I'm on board - expect a thumbs up from me.
I'm going to play with this code a little before I give an official thumbs up, but I don't expect I'll want any changes.
Great job on this!
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.
I think this looks great, but I'd suggest we don't change the interface to the checkSign
functions in this PR. Other than that I think this is good to go.
src/ripple/protocol/STTx.h
Outdated
@@ -131,7 +132,7 @@ class STTx final : public STObject, public CountedObject<STTx> | |||
@return `true` if valid signature. If invalid, the error message string. | |||
*/ | |||
enum class RequireFullyCanonicalSig : bool { no, yes }; | |||
std::pair<bool, std::string> | |||
Expected<bool, std::string> |
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.
I don't love this interface:
- The "success" variant can only ever be true.
- Types like
Expected<bool,...>
andoptional<bool>
have both an "outer bool" and an "inner bool", which make them error prone.
My suggestion is to punt on changing this interface in this PR. Let's focus on Expected
itself, and use deserialize
as a good test-case interface to change. While we should change this one too, I think I'd rather do it in a different PR.
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.
I think I want an Expected<void,std::string>
here. But of course the current Expected
interface can't handle a void
success type. It looks like the standard version does. Maybe we should add support for our wrapper?
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, I think Expected<void, T>
is a good solution to this interface. And, yes, it looks like std::expected
supports this model. Thanks for the great suggestion!
A quick experiment suggests that a partial specialization of Expected
should do the trick and add very little additional code. If that experiment continues to be successful I'll add a FOLD
commit with those changes and some additional unit tests. If the experiment fails I'll report back the reason for the failure.
src/ripple/protocol/STTx.h
Outdated
@@ -150,10 +151,10 @@ class STTx final : public STObject, public CountedObject<STTx> | |||
std::string const& escapedMetaData) const; | |||
|
|||
private: | |||
std::pair<bool, std::string> | |||
Expected<bool, std::string> |
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 comment above.
src/ripple/protocol/STTx.h
Outdated
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const; | ||
|
||
std::pair<bool, std::string> | ||
Expected<bool, std::string> |
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 comment above.
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.
This looks good to me. I am agnostic concerning what commit initial integration happens in.
I initially thought we should try to implement the proposed spec. But after looking into it I changed my mind.
- The proposed spec isn't yet mature enough, and
- The implementation is a bigger project than we want to take on and maintain.
I pushed a commit with a partial specialization for |
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.
Thanks for coding up the void
specialization. I had one comment about removing the true_type
constructor and replacing it with a default constructor. Other than that it looks great.
src/ripple/basics/Expected.h
Outdated
// We'd like to avoid a default constructor. That means someone needs | ||
// explicitly say they intent to return success. Seems like | ||
// std::true_type is a good way to do that... | ||
constexpr Expected(std::true_type) : Base(boost::outcome_v2::success()) |
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.
It looks like the proposed standard expected
has a default constructor for a void
return (ref: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0323r9.html#expected.object.ctor). I think a default constructor makes sense in this case, and given that the standard has it, I'd like to remove this constructor and replace it with a default constructor.
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.
Thanks for catching the default constructor for Expected<void, E>
. I looked for something like boost::outcome_v2::success()
in the paper and didn't find it, so I punted. Matching the currently proposed version of the paper seems like the best approach. I've included an additional [FOLD]
to make this 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.
👍 Great job on this!
I'm approving this now, but please see my comment about adding the assert
. I'm approving now because however you want to resolve that is fine with me (including keeping it as-is).
src/ripple/basics/base_uint.h
Outdated
@@ -212,20 +213,16 @@ class base_uint | |||
return ParseResult::okay; | |||
}; | |||
|
|||
std::pair<ParseResult, decltype(data_)> ret{ParseResult::okay, {}}; | |||
decltype(data_) out{}; | |||
std::size_t i = 0u; |
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: Consider moving the var i
decl to just before the while
loop; I'd probably also call this variable ret
instead of out
(you could have an auto out = ret.data()
if you wanted to keep something closer to the original code too), but also fine as-is.
src/ripple/basics/base_uint.h
Outdated
|
||
if (result.first == ParseResult::badChar) | ||
assert(result.error() == ParseResult::badChar); |
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.
Why does badChar
assert but badLength
doesn't assert? Consider losing the assert
. It's changing the behavior of debug builds, and the change doesn't involve the major focus of this PR - which is expected
. I think it's better if PRs focus on one thing only.
Having said that, I only weakly object. If you feel strongly, then it's your call.
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.
The assert was intended to be a way to guarantee that all of the non-okay
ParseResult
values are covered. We can see that by inspection now. But in case another error code was added in the future the assert
would let us know that the Throw
had the wrong text. But I don't feel strongly about it. I'm fine with removing the assert
.
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.
Still 👍 from me
9a3b50e
to
10b0518
Compare
I squashed and pushed and expected CI to run. Apparently Travis needs a payment before CI will run: https://app.travis-ci.com/github/ripple/rippled/requests I'll wait until CI has passed before marking the pull request as passed. |
Also integrates use of ripple::Expected into the code base.
10b0518
to
2b3e454
Compare
Re-pushed to kick Travis into gear. |
Make a Stand-in For
std::expected
Available in the Code Basestd::expected
is a proposed type for the standard library. The current paper forstd::expected
is: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.htmlThe interface of
std::expected
looks great to me. It's like astd::optional
that tells you why something came back empty. I'd like to be able to use it in the code base without waiting for us to adopt C++23.Context of Change
Boost does not directly provide an interface like
std::expected
. However Boost has something similar calledboost::outcome_v2::result
. I've coded a fairly thin wrapper aroundboost::outcome_v2::result
that provides the characteristics ofstd::expected
that I particularly like. I've named that wrapperripple::Expected
.That wrapper, and unit tests for it, are in the first commit.
My initial impression was that
Expected
would be immediately usable in significant portions of the code base. So I integratedExpected
into our code base. To my surprise I found relatively few places where I could directly drop inExpected
as a replacement for returning astd::pair
where one of the pair members was the reason for the failure. But I did find a few. You can find that integration in the second commit.Reviewers should consider two things:
Expected
is not essential to the code base, should we still incorporate it? I think we should. But you may not agreeExpected
into the code base, do you see any coding errors or places for improvement?For what it's worth, the
std::expected
paper is on track to go into C++23 and has passed the Library Evolution working group. But there's some risk because it has not yet survived the Library (wording) working group.Type of Change
This is an internals-only change. It does not need to be mentioned in the release notes.
Before / After
From an interface perspective, an implementation returning an
Expected
changes something sorta like this ...... to this ...
At the call site this ...
... becomes this ...
The examples have not been run through a compiler, so there may be bugs.
Test Plan
Unit tests for
Expected
are included. The integration also builds confidence that theExpected
wrapper is behaving as expected.Future Tasks
Presuming
std::expected
makes it into C++23 it will make sense to removeripple::Expected
from the code base and replace its usage withstd::expected
. There shouldn't be significant usage changes tostd::expected
, since it has passed through Library Evolution. So the replacement should be straight forward at that time.