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

impl PartialEq for comparing primitive integer {u,i}N with NonZero{U,I}N #81181

Closed
inodentry opened this issue Jan 19, 2021 · 10 comments
Closed
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@inodentry
Copy link

It should be possible to directly compare primitive integers with their NonZero equivalents:

let x = 10u8;
let y = NonZeroU8::new(x).unwrap();

// this should work:
if x == y {
    // ...
}

Currently, such code produces a type mismatch compiler error due to PartialEq not being implemented for these types, to allow it.

Such a comparison seems trivial. It should be non-controversial to add support for it?

@leonardo-m
Copy link

Do you have an use case?

@LeSeulArtichaut LeSeulArtichaut added C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Jan 19, 2021
@inodentry
Copy link
Author

inodentry commented Jan 19, 2021

Yes, I came here after being surprised by this in my own project.

I have an enum where I use NonZeroU8 to take advantage of the size packing optimization to keep it as one byte:

enum PlayerId {
    Spectator,
    Player(NonZeroU8),
}

This is useful for correctness (because I don't consider 0 to be a valid player id in my multiplayer game), for it to be used in binary messages (have it optimized to a no-op), and to save memory (in arrays containing many of them, or to not bloat the stack). Basically, the standard advantages of the NonZero types (what motivated them being added to Rust).

However, it is quite annoying to use in practice, because I cannot compare it with arbitrary u8 (which includes literals!).

I have to suffer the endless papercuts with type conversions (having to type u8::from(id) everywhere), because I want NonZeroU8 in my enum (for semantic correctness and memory layout).

I am sure many people have other similar applications where using a NonZero type makes sense.

It seems silly to not support PartialEq for directly comparing these types. A NonZeroU8 is fully compatible and effectively equivalent to an u8.

I mean, it's a papercut, not a major blocker, but it's silly that I have to deal with it. It makes using these standard wrapper types a hassle and unergonomic.

@leonardo-m
Copy link

(having to type u8::from(id) everywhere)

There's also the option of using id.get()

@slerpyyy
Copy link

Implementations of PartialEq which compare values of different types are fairly rare in the std crate and may be surprising to some users. I definitely see the appeal to having this impl in your case, but I'm not convinced adding abstractions to the language which hide safety wrappers is such a good idea in general.

That being said, how about you add these abstractions on your end directly?

Playground

@inodentry
Copy link
Author

Thanks for the suggestion. You are right that there are things I can do on my end to improve the experience.

I guess adding such "conveniences" to the language is more controversial than I imagined.

@inodentry
Copy link
Author

inodentry commented Jan 21, 2021

Implementations of PartialEq which compare values of different types are fairly rare in the std crate and may be surprising to some users.

I went to look at the docs to find examples, and found that there are already some such implementations:

  • Path, PathBuf, OsString, OsStr can all be compared with each other (which makes sense since the underlying string is the same)
  • ditto for slices, arrays, Vecs, VecDeques
  • OsString/OsStr can be compared with &str, which seems more controversial, as str is guaranteed utf-8, but os strings may not be. This is an example of a convenience abstraction that may hide underlying details important for safety.

That considered, I don't see why comparing NonZero{I,U}* with {i,u}* is any different in that regard. It seems obvious enough that it shouldn't be surprising, and I can't think of how it could actually compromise safety.

@cuviper
Copy link
Member

cuviper commented Jan 21, 2021

Having more PartialEq will break type inference in some cases, for example:

struct Foo;

impl PartialEq<Foo> for u32 {
    fn eq(&self, _: &Foo) -> bool {
        false
    }
}

fn main() {
    let x = 42_u32;
    let y = 0u8;
    
    assert!(x != y.into());
}
error[E0283]: type annotations needed
  --> src/main.rs:13:15
   |
13 |     assert!(x != y.into());
   |               ^^ -------- this method call resolves to `T`
   |               |
   |               cannot infer type
   |
   = note: cannot satisfy `u32: PartialEq<_>`

I ran into this scenario myself -- rust-num/num-bigint#150.

@osa1
Copy link
Contributor

osa1 commented Jan 22, 2021

Would it be possible to run crater with this change to get an idea on how much breakage this would cause?

@slerpyyy
Copy link

I added an implementation (#81266) in case you to try things out.

Looking at the CI, it seems things are already falling appart:

error[E0283]: type annotations needed
    --> /cargo/registry/src/github.com-1ecc6299db9ec823/miniz_oxide-0.4.0/src/deflate/core.rs:1871:43
     |
1871 |         let far_and_small = cur_match_len == MIN_MATCH_LEN.into() && cur_match_dist >= 8 * 1024;
     |                                           ^^ -------------------- this method call resolves to `T`
     |                                           |
     |                                           cannot infer type
     |
     = note: cannot satisfy `u32: PartialEq<_>`

error[E0283]: type annotations needed
    --> /cargo/registry/src/github.com-1ecc6299db9ec823/miniz_oxide-0.4.0/src/deflate/core.rs:2037:43
     |
2037 |                         || (cur_match_len == MIN_MATCH_LEN.into() && cur_match_dist >= 8 * 1024)
     |                                           ^^ -------------------- this method call resolves to `T`
     |                                           |
     |                                           cannot infer type
     |
     = note: cannot satisfy `u32: PartialEq<_>`

error: aborting due to 2 previous errors

@jyn514
Copy link
Member

jyn514 commented Apr 16, 2021

I'm going to close this issue since adding the impl would cause a lot of inferrence issues.

@jyn514 jyn514 closed this as completed Apr 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants