-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Move Range*::contains to a single default impl on RangeBounds #49130
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
src/liballoc/range.rs
Outdated
/// assert!(!(..=5).contains(6)); | ||
/// ``` | ||
#[unstable(feature = "range_contains", reason = "recently added as per RFC", issue = "32311")] | ||
fn contains(&self, item: T) -> bool |
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.
Let's make the method generic (e.g. fn contains<U>(&self, item: &U) -> bool
) so that we can check for containment of types which are not directly the same time as the range (i.e. heterogeneous comparisons).
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.
Oh that's what was meant, makes sense.
I think if this change is being made, then it's the right time to make the other two changes discussed in the issue at the same time (changing method signatures later can be a pain; it's much better to get it right first time): i.e. taking a reference and being generic over the item type. I think the Wrapping issue is less important, because technically such a case is not permitted according to the documentation. |
Makes sense to me. I've made those changes locally and am running the tests now, will push once they finish. |
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 to see this making progress!
src/liballoc/range.rs
Outdated
/// assert!( (..=5).contains(&-1_000_000_000)); | ||
/// assert!( (..=5).contains(&5)); | ||
/// assert!(!(..=5).contains(&6)); | ||
/// ``` |
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.
Because this is bound only to PartialOrd, I suggest you include some NAN examples in here too. For example, I would expect !(0.0..1.0).contains(&f32::NAN)
, !(0.0..f32::NAN).contains(&0.5)
, and !(f32::NAN..1.0).contains(&0.5)
. (I think that's what the previous code did.)
I think that will mean changing the implementation, since a range like NAN..NAN
will never get to the return false
lines. Maybe keep the positive phrasing of the originals, something like
(match self.start() {
Included(ref start) => item >= *start,
Excluded(ref start) => item > *start,
Unbounded => true,
})
&&
(match self.end() {
Included(ref end) => item <= *end,
Excluded(ref end) => item < *end,
Unbounded => true,
})
src/liballoc/range.rs
Outdated
#[unstable(feature = "range_contains", reason = "recently added as per RFC", issue = "32311")] | ||
fn contains<U>(&self, item: &U) -> bool | ||
where | ||
U: ?Sized + PartialOrd<T>, |
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.
Something I don't understand coherence well enough to answer: how does U: PartialOrd<T>
as a bound compare to T: PartialOrd<U>
? Would we prefer to make it easier for T
or U
to be a custom type, and which one makes that easier? Should this just bound both of them for now?
These changes all seem great to me, thanks @smmalis37! I wonder though if it may also be best to leave the inherent methods on the various common range types? That way you can still use the method without having to import the trait (but the implementation would simply delegate to the trait implementation). I think I also agree with @scottmcm as well in that the direction of the type parameters may matter here and I'd probably expect |
Though |
@alexcrichton Finally finding time to work on this again. I like your suggestion, however the trait is currently defined in liballoc while the Range* types are defined in libcore. So I don't think it's possible for the types to delegate to the trait without moving it into libcore, which would also require moving Bound into libcore. Is my understanding correct? Should I go make that move? |
I just noticed #49163, guess I'm waiting on that to be merged. |
Now that RangeBounds is in the same module as the Range* types, do we still want to add the inherent method as well? |
☔ The latest upstream changes (presumably #49163) made this pull request unmergeable. Please resolve the merge conflicts. |
Ping from triage, @smmalis37 ! Will you have time to pick this back up?
This is now merged. |
Yep, I'm working on it now. |
Pardon the force push and removing the past commits, but it needed to be rebased anyways. The PR description has also been updated. |
#[unstable(feature = "range_contains", reason = "recently added as per RFC", issue = "32311")] | ||
fn contains<U>(&self, item: &U) -> bool | ||
where | ||
T: PartialOrd<U>, |
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.
Re-mentioning the T: PartialOrd<U>
vs U: PartialOrd<T>
vs both discussion, since I'm not sure it was fully resolved.
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 we agreed on not U: PartialOrd<T>
, but the other two are still options.
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'm still in favour of where T: PartialOrd<U>, U: PartialOrd<T>
for compatibility. For anyone implementing these traits correctly, there'll be no inconvenience, but it makes it easier in the future to modify in a way that's backwards-compatible.
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.
There's definitely the advantage there of making sure that everything is implemented correctly, however it does raise the question of consistency. Do we think this is a small enough issue that it's more worthwhile to be consistent with the rest of std? Are there plans to address this issue in a more broad way that would apply everywhere?
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 rest of std isn't consistent. For example, Vec::contains
isn't even generic over the search type. The issue is that, in the past, the methods weren't defined generally enough, and when people come back to try to fix that, there are type inference issues.
The general practice now is "make new methods behave correctly" and we'll try to fix the old ones when it's possible (e.g. with #27336 and #46946). These aren't going to be done very soon, though, so as someone who has been bitten by bad signatures in the past, a little more inconsistency is far preferable.
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.
Sounds good to me, I'll make the change once I'm home from work.
Any good reason to not define |
Such a proposal hasn't gone through the RFC process yet to my knowledge. This is just a tweak/update to one that already has. |
This is no longer waiting-on-author, but waiting-on-review. Not sure how to change the label. |
Ping from triage! Can @alexcrichton (or someone else from @rust-lang/libs) review this? |
@smmalis37 ah my apologies, sorry about that! @bors: r+ |
📌 Commit 51f24ec has been approved by |
Move Range*::contains to a single default impl on RangeBounds Per the ongoing discussion in #32311. This is my first PR to Rust (woo!), so I don't know if this requires an amendment to the original range_contains RFC, or not, or if we can just do a psuedo-RFC here. While this may no longer follow the explicit decision made in that RFC, I believe this better follows its spirit by adding the new contains method to all Ranges. It also allows users to be generic over all ranges and use this method without writing it themselves (my personal desired use case). This also somewhat answers the unanswered question about Wrapping ranges in the above issue by instead just punting it to the question of what those types should return for start() & end(), or if they should implement RangeArgument at all. Those types could also implement their own contains method without implementing this trait, in which case the question remains the same. This does add a new contains method to types that already implemented RangeArgument but not contains. These types are RangeFull, (Bound<T>, Bound<T>), (Bound<&'a T>, Bound<&'a T>). No tests have been added for these types yet. No inherent method has been added either. r? @alexcrichton
☀️ Test successful - status-appveyor, status-travis |
Tested on commit rust-lang/rust@49317cd. Direct link to PR: <rust-lang/rust#49130> 💔 rls on windows: test-pass → build-fail (cc @nrc). 💔 rls on linux: test-pass → build-fail (cc @nrc). 💔 rustfmt on windows: test-pass → build-fail (cc @nrc). 💔 rustfmt on linux: test-pass → build-fail (cc @nrc).
Per the ongoing discussion in #32311.
This is my first PR to Rust (woo!), so I don't know if this requires an amendment to the original range_contains RFC, or not, or if we can just do a psuedo-RFC here. While this may no longer follow the explicit decision made in that RFC, I believe this better follows its spirit by adding the new contains method to all Ranges. It also allows users to be generic over all ranges and use this method without writing it themselves (my personal desired use case).
This also somewhat answers the unanswered question about Wrapping ranges in the above issue by instead just punting it to the question of what those types should return for start() & end(), or if they should implement RangeArgument at all. Those types could also implement their own contains method without implementing this trait, in which case the question remains the same.
This does add a new contains method to types that already implemented RangeArgument but not contains. These types are RangeFull, (Bound, Bound), (Bound<&'a T>, Bound<&'a T>). No tests have been added for these types yet. No inherent method has been added either.
r? @alexcrichton