-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Overloaded comparison traits #19167
Overloaded comparison traits #19167
Conversation
I tried to cover all the slice comparisons with the following blanket impl<T, Sized? Lhs: AsSlice<T>, Sized? Rhs: AsSlice<T>> PartialEq<Rhs> for Lhs { /* ... */ } But sadly, it conflicts with the That blanket I guess I'll manually implement |
Whoa that is super weird ( |
@alexcrichton BTW, the current So, I actually changed the bound from // A heap allocation just to get a the `==` operator to type check!
if matches.opt_strs("passes").as_slice() == &["list".to_string()] { /* .. */ } to if matches.opt_strs("passes") == ["list"] { /* .. */ } I'll push the updated branch in a bit... |
I've pushed the updated branch, I've added
In the case of slices/vectors you can use There is some fallout that I'm still fixing, these are common issues that I have encounter:
These Thoughts up to this point? Do we want all these One more thing, |
fn ne(&self, other: &Rhs) -> bool { PartialEq::ne(&**self, &**other) } | ||
} | ||
|
||
impl<'a> PartialEq<String> for &'a 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.
In theory, shouldn't this also be PartialEq<Rhs> where Rhs: Deref<str>
? I'm real bad about thinking about coherence violations though!
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 tried that, it conflicts with this other impl:
impl<Sized? A, Sized? B> PartialEq<&B> for &A { /* .. */ }
because &str
implements Deref<str>
. In other words, when Lhs = Rhs = &str
and A = B = str
then &str
gets two implementations.
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.
Is there a reason this is for &'a str
rather than str
?
In the long run, with negative where
clauses, I think we'll be able to do better with asymmetric deref impls.
For now, I think it might be best to do just impl PartialEq<str> for String
and impl PartialEq<String> for str
to keep equality symmetric.
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.
Is there a reason this is for &'a str rather than str?
Yes, with this you can do string == "Hello"
. LHS has type String
, RHS has type &str
. If I change it to the the unsized version, that comparison won't work, instead you'll have to write string == *"Hello"
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.
Make sense; presumably &string == "Hello"
would work as well. The basic problem is that the blanket impl for adding a layer of &
has to add it to both sides, or else it runs afoul of coherence.
Anyway, I think this is fine for now, and the main thing is for the equality impls to be symmetric.
@japaric this seems pretty awesome, I'm quite excited about this! Some thoughts about the points you raised:
This seems ok, I'm not sure it's all that common of a pattern. In general this is a worry of making operations more generic than they used to be which is to require more type annotations because inference has less to go on.
This is a little worrying, as I would definitely be expecting this to work. cc @aturon, this is one of the unfortunate spots where coercions seem like they should work but end up not working out.
This should not be a common pattern at all. A function like this should definitely be in a helper function to make the |
Perhaps a little late, but it just occurred to me that we could
Turns out the transmutes were not necessary so I removed all of them. BTW, I'm referring to these tests. Another pattern that broke is: |
I have updated the branch, it now fully passes |
Thanks @japaric -- I plan to review this today or tomorrow. |
fn ne(&self, other: &Rhs) -> bool { PartialEq::ne(&**self, &**other) } | ||
} | ||
|
||
impl<'a, A, B> PartialEq<Vec<B>> for &'a [A] where A: PartialEq<B> { |
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.
Same issues here as with strings: it's not clear why there's an impl on &[A]
rather than [A]
, and I think it's best if the impls are symmetric (even if that means they're less flexible for now).
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 previous comment. (The one about String/&str)
This looks good to me, though I left some comments about a few concerns. I'm not particularly worried about the extra type annotations or shifting with arrays, since almost all cases of these were in tests. Also, @alexcrichton, there's been a bit of churn about exactly what coercions may apply to arrays vs slices; I think what this PR is doing there is pretty reasonable. I especially liked the point about Anyway, let me know what you think about the comments I left, and nice work as usual :) |
@aturon Added commit deprecating |
Hey, so sorry for the slow response -- I've been offline for the US Thanksgiving holiday. This largely looks good, but I continue to be a bit worried about asymmetric implementations of |
At some point I tried a "commutative" blanket This is what I tried (see it in the playpen) impl<Lhs, Rhs> PartialEq<Rhs> for Lhs where Rhs: PartialEq<Lhs> {
fn eq(&self, rhs: &Rhs) -> bool {
rhs.eq(self)
}
}
struct Foo;
struct Bar;
fn overflow() {
Foo == Bar;
} And I got: eq.rs:22:5: 22:15 error: overflow evaluating the trait `Sized` for the type `_`
eq.rs:22 Foo == Bar;
^~~~~~~~~~
eq.rs:22:5: 22:15 error: overflow evaluating the trait `Sized` for the type `Foo`
eq.rs:22 Foo == Bar;
^~~~~~~~~~
eq.rs:22:5: 22:15 error: overflow evaluating the trait `PartialEq<_>` for the type `Foo`
eq.rs:22 Foo == Bar;
^~~~~~~~~~
error: aborting due to 3 previous errors I think that may work when/if we get either negative equality bounds: Until that, we'll (and the same applies to library authors) have to take care of manually implementing both |
@japaric Right, we can't provide a blanket commutative impl, given the way that dispatch works. But my specific worry was about cases to do with e.g. string slices like the following: impl<Rhs> PartialEq<Rhs> for String where Rhs: Deref<str> { ... }
impl<'a> PartialEq<String> for &'a str { ... } These are not obviously symmetric to me, but I'm probably missing something. |
Yeah, we can't write it in a symmetric way because we get conflicting implementations: impl<Rhs> PartialEq<Rhs> for String where Rhs: Deref<str> { ... }
impl<Lhs> PartialEq<String> for Lhs where Lhs: Deref<str> { ... } Collides when The trick is that impl<Rhs> PartialEq<Rhs> for String where Rhs: Deref<str> { ... } takes care of impl<'a> PartialEq<String> for &'a str { ... } takes care of impl<'a, 'b, Sized? A, Sized? B> PartialEq<&'b B> for &'a A { ... } takes care of (That reminds me, that I should add 2 more Alternatively, we could manually write all the combinations without using the |
@japaric Right, so I understood that having |
Yes, it does. If you do It's a tradeoff:
|
@japaric OK, I think we're on the same page now. As far as the tradeoff goes, my thoughts are:
I just talked to @alexcrichton and he agrees with the above. Can you change this PR to guarantee symmetry in all cases? For dealing with the combinatorial explosion, you could consider using macros. |
Rebased and passes r? @aturon |
@japaric Looks great to me! r=me after a squash. |
- String == &str == CowString - Vec == &[T] == &mut [T] == [T, ..N] == CowVec - InternedString == &str
@aturon squashed! |
Comparison traits have gained an `Rhs` input parameter that defaults to `Self`. And now the comparison operators can be overloaded to work between different types. In particular, this PR allows the following operations (and their commutative versions): - `&str` == `String` == `CowString` - `&[A]` == `&mut [B]` == `Vec<C>` == `CowVec<D>` == `[E, ..N]` (for `N` up to 32) - `&mut A` == `&B` (for `Sized` `A` and `B`) Where `A`, `B`, `C`, `D`, `E` may be different types that implement `PartialEq`. For example, these comparisons are now valid: `string == "foo"`, and `vec_of_strings == ["Hello", "world"]`. [breaking-change]s Since the `==` may now work on different types, operations that relied on the old "same type restriction" to drive type inference, will need to be type annotated. These are the most common fallout cases: - `some_vec == some_iter.collect()`: `collect` needs to be type annotated: `collect::<Vec<_>>()` - `slice == &[a, b, c]`: RHS doesn't get coerced to an slice, use an array instead `[a, b, c]` - `lhs == []`: Change expression to `lhs.is_empty()` - `lhs == some_generic_function()`: Type annotate the RHS as necessary cc #19148 r? @aturon
Now that we have an overloaded comparison (`==`) operator, and that `Vec`/`String` deref to `[T]`/`str` on method calls, many `as_slice()`/`as_mut_slice()`/`to_string()` calls have become redundant. This patch removes them. These were the most common patterns: - `assert_eq(test_output.as_slice(), "ground truth")` -> `assert_eq(test_output, "ground truth")` - `assert_eq(test_output, "ground truth".to_string())` -> `assert_eq(test_output, "ground truth")` - `vec.as_mut_slice().sort()` -> `vec.sort()` - `vec.as_slice().slice(from, to)` -> `vec.slice(from_to)` --- Note that e.g. `a_string.push_str(b_string.as_slice())` has been left untouched in this PR, since we first need to settle down whether we want to favor the `&*b_string` or the `b_string[]` notation. This is rebased on top of #19167 cc @alexcrichton @aturon
Now that we have an overloaded comparison (`==`) operator, and that `Vec`/`String` deref to `[T]`/`str` on method calls, many `as_slice()`/`as_mut_slice()`/`to_string()` calls have become redundant. This patch removes them. These were the most common patterns: - `assert_eq(test_output.as_slice(), "ground truth")` -> `assert_eq(test_output, "ground truth")` - `assert_eq(test_output, "ground truth".to_string())` -> `assert_eq(test_output, "ground truth")` - `vec.as_mut_slice().sort()` -> `vec.sort()` - `vec.as_slice().slice(from, to)` -> `vec.slice(from_to)` --- Note that e.g. `a_string.push_str(b_string.as_slice())` has been left untouched in this PR, since we first need to settle down whether we want to favor the `&*b_string` or the `b_string[]` notation. This is rebased on top of #19167 cc @alexcrichton @aturon
Now that we have an overloaded comparison (`==`) operator, and that `Vec`/`String` deref to `[T]`/`str` on method calls, many `as_slice()`/`as_mut_slice()`/`to_string()` calls have become redundant. This patch removes them. These were the most common patterns: - `assert_eq(test_output.as_slice(), "ground truth")` -> `assert_eq(test_output, "ground truth")` - `assert_eq(test_output, "ground truth".to_string())` -> `assert_eq(test_output, "ground truth")` - `vec.as_mut_slice().sort()` -> `vec.sort()` - `vec.as_slice().slice(from, to)` -> `vec.slice(from_to)` --- Note that e.g. `a_string.push_str(b_string.as_slice())` has been left untouched in this PR, since we first need to settle down whether we want to favor the `&*b_string` or the `b_string[]` notation. This is rebased on top of #19167 cc @alexcrichton @aturon
fix: Fix detection of ref patterns for path patterns
Comparison traits have gained an
Rhs
input parameter that defaults toSelf
. And now the comparison operators can be overloaded to work between different types. In particular, this PR allows the following operations (and their commutative versions):&str
==String
==CowString
&[A]
==&mut [B]
==Vec<C>
==CowVec<D>
==[E, ..N]
(forN
up to 32)&mut A
==&B
(forSized
A
andB
)Where
A
,B
,C
,D
,E
may be different types that implementPartialEq
. For example, these comparisons are now valid:string == "foo"
, andvec_of_strings == ["Hello", "world"]
.[breaking-change]s
Since the
==
may now work on different types, operations that relied on the old "same type restriction" to drive type inference, will need to be type annotated. These are the most common fallout cases:some_vec == some_iter.collect()
:collect
needs to be type annotated:collect::<Vec<_>>()
slice == &[a, b, c]
: RHS doesn't get coerced to an slice, use an array instead[a, b, c]
lhs == []
: Change expression tolhs.is_empty()
lhs == some_generic_function()
: Type annotate the RHS as necessarycc #19148
r? @aturon