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

Implement Bounded, ToPrimitive, NumCast, FromPrimitive for NonZero* #334

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

acrrd
Copy link

@acrrd acrrd commented Aug 29, 2024

I have a use case where I convert to and from primitive, and I need to involve NonZero* types as well.
Currently, I have a function that uses these traits for primitive types, and I have additional functions for each NonZero* that I need. It would be nice to get rid of them and have only one.

I can create on PR per trait implementation if it's preferred. This PR has one commit per trait implementation.

@acrrd acrrd force-pushed the support_nonzero branch 4 times, most recently from 03bb963 to eee8ac0 Compare August 30, 2024 11:11
@foolishell
Copy link

@acrrd
Thank you so much for this pull request. If possible, could you also add support for AsPrimitive?

@tarcieri
Copy link
Contributor

Curious about https://doc.rust-lang.org/stable/core/num/struct.NonZero.html as well (might need an MSRV-preserving feature-gate)

@acrrd
Copy link
Author

acrrd commented Sep 17, 2024

@acrrd Thank you so much for this pull request. If possible, could you also add support for AsPrimitive?

done

@acrrd
Copy link
Author

acrrd commented Sep 17, 2024

Curious about https://doc.rust-lang.org/stable/core/num/struct.NonZero.html as well (might need an MSRV-preserving feature-gate)

This needs nightly, NonZero is only usable with types that implement ZeroablePrimitive. This trait is usable only in nightly.
When stabilized it will reduce the needed code to implement NonZero, but now is not usable.

@tarcieri
Copy link
Contributor

@acrrd though the ZeroablePrimitive trait itself is unstable, there are impls for it for the core integer types already which are usable on stable Rust:

https://doc.rust-lang.org/stable/core/num/trait.ZeroablePrimitive.html#implementors

Quite a bit of the functionality is already stable, as is noted in the feature gates in the rustdoc. Here's a small example:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9c4126921b16b781588732fd26d050a4

@cuviper
Copy link
Member

cuviper commented Sep 17, 2024

The old types are just aliases now, literally type NonZeroI32 = NonZero<i32>; etc. So implementing traits here on those aliases has exactly the same effect as implementing NonZero<T> for specific T.

@cuviper
Copy link
Member

cuviper commented Sep 17, 2024

I'm torn on AsPrimitive -- right now that is only implement for types where you could literally write x as T in non-generic settings. It's not sealed though, and the docs do mention how newtypes should behave too.

So if we add that, shouldn't we also do it for Wrapping? (and Saturating, if we get to that at all)

@acrrd
Copy link
Author

acrrd commented Sep 18, 2024

I'm torn on AsPrimitive -- right now that is only implement for types where you could literally write x as T in non-generic settings. It's not sealed though, and the docs do mention how newtypes should behave too.

It was requested in #335, they have a use case for it. Let me know if you want to keep it.

So if we add that, shouldn't we also do it for Wrapping? (and Saturating, if we get to that at all)

We can't. The trait requires Add,Sub, etc., which are not implemented for NonZero*. This is related to #274.

@cuviper
Copy link
Member

cuviper commented Sep 18, 2024

I'm torn on AsPrimitive -- [...]

It was requested in #335, they have a use case for it. Let me know if you want to keep it.

I'm going to respond there, because I think that use is also problematic, even aside from NonZero. Let's drop it here for now.

So if we add that, shouldn't we also do it for Wrapping? (and Saturating, if we get to that at all)

We can't. The trait requires Add,Sub, etc., which are not implemented for NonZero*. This is related to #274.

I wasn't talking about any other traits, only impl<T, U> AsPrimitive<T> for core::num::Wrapping<U>. But that deserves a separate PR, if we even decide that's worthwhile.

@acrrd
Copy link
Author

acrrd commented Sep 18, 2024

I'm going to respond there, because I think that use is also problematic, even aside from NonZero. Let's drop it here for now.

done

I wasn't talking about any other traits, only impl<T, U> AsPrimitive<T> for core::num::Wrapping<U>. But that deserves a separate PR, if we even decide that's worthwhile.

I see it now, sorry

@marshrayms
Copy link

marshrayms commented Sep 26, 2024

To me, a big part of the value of add-on crates like the rust-num collection is that they can help make up where the base language falls short.

I'm torn on AsPrimitive -- right now that is only implement for types where you could literally write x as T in non-generic settings. It's not sealed though, and the docs do mention how newtypes should behave too.

For example, the concept you mentioned of "types where you could literally write x as T" is not expressible using any combination of traits from the standard library.

So if we add that, shouldn't we also do it for Wrapping? (and Saturating, if we get to that at all)

I think the difference is that NonZero* is a newtype which expresses an inherent property of the value itself. They are useful as type system constraints.

Whereas Wrapping and Saturating express nothing about the set of values they represent, they are simply used to select different sets of common functions via operator overloading. Purely syntactic convenience.

@cuviper
Copy link
Member

cuviper commented Sep 26, 2024

To me, a big part of the value of add-on crates like the rust-num collection is that they can help make up where the base language falls short.

I'm torn on AsPrimitive -- right now that is only implement for types where you could literally write x as T in non-generic settings. It's not sealed though, and the docs do mention how newtypes should behave too.

For example, the concept you mentioned of "types where you could literally write x as T" is not expressible using any combination of traits from the standard library.

This seems consistent to me -- AsPrimitive is currently filling that gap in the base language by making it expressible as a trait, though you only get .as_() instead of a real as in that generic code. We can choose to extend the domain of that trait as well, and maybe we should, but I think we should do that separately.

Anyway, let me give this PR an actual review...

src/bounds.rs Outdated
Comment on lines 77 to 82
const $i: $t = {
let arr = [$v];
let idx = if $v != 0 { 0 } else { 1 };

unsafe { <$t>::new_unchecked(arr[idx]) }
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done safely -- NonZero::new is const since 1.47, and const panic! since 1.57. (Our MSRV is 1.60.)

Suggested change
const $i: $t = {
let arr = [$v];
let idx = if $v != 0 { 0 } else { 1 };
unsafe { <$t>::new_unchecked(arr[idx]) }
};
const $i: $t = match <$t>::new($v) {
Some(nz) => nz,
None => panic!("bad nonzero bound!"),
};

And once our MSRV is 1.70 we should use the inherent consts, so please leave a comment for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, done.

src/cast.rs Outdated
macro_rules! impl_to_primitive_nonzero_to_method {
($SrcT:ident : $( $(#[$cfg:meta])* fn $method:ident -> $DstT:ident ; )*) => {$(
#[inline]
$(#[$cfg])*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you copied the cfg from the other macros, but it's vestigial, from the days when i128 was conditional. We definitely shouldn't propagate that, but could you clean up the others too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure what it was so I just kept it. Removed here and from other macros

src/cast.rs Outdated

#[inline]
fn to_f32(&self) -> Option<f32> {
Some(self.get() as f32)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this forwarding to the inner to_f32 as well?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I think I copied from the other macro and didn't notice here I could put everything together.

src/cast.rs Outdated
@@ -572,6 +631,85 @@ impl_from_primitive!(u128, to_u128);
impl_from_primitive!(f32, to_f32);
impl_from_primitive!(f64, to_f64);

macro_rules! impl_from_primitive_nonzero {
($T:ty, $to_ty:ident) => {
#[allow(deprecated)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is also vestigial, way back from commit 03db5c9 when it was copied from its deprecated state in the standard library.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants