#notzero: NonZero from constants

31 messages · Page 1 of 1 (latest)

idle oak
#

https://docs.rs/notzero/latest/notzero/
https://github.com/rodrimati1992/notzero/
this crate provides an nz macro for constructing std::num::NonZero* from an integer constant.

The difference with other crates that provide similar macros is that this can infer the type of the argument from the expected NonZero type,
e.g.: let _: NonZeroU8 = nz!(3); infers that 3 is a u8 because it's expected to return a NonZeroU8.

~~caveat: this crate uses the inline_const unstable feature, so it'll require the nightly compiler so long as that feature is unstable.~~this feature stabilized in Rust 1.79.0

mental hull
#

Really good work. My only complaint is about the need for an unstable feature.

#

However, I've made a similar thing that doesn't need the unstable feature by abusing unions at compile time

#

however, mine doesn't support non-integer types like yours does

#

?play

use std::num::*;

/// Constructs a dispatcher from an expression of an arbitrary int type.
/// Used to convert to an erased expression and gives an id to get the proper type back
#[doc(hidden)]
pub struct __NzDispatcherConstructor<T>(T);

/// Does the actual conversion of the expression to a `NonZero` type
#[doc(hidden)]
pub struct __NzDispatcher<const N: usize>(AnyInt);

/// Maps a `__NzDispatcher` to a `NonZero` type
#[doc(hidden)]
pub trait __NzType {
    type Nz;
}

impl<T> __NzDispatcherConstructor<T> {
    pub const fn __new(val: T) -> Self {
        Self(val)
    }
}

macro_rules! impl_nz {
    ([] [$($union_fields:tt)*] [$($counter:tt)*] ) => {
        /// An expression evaluating to an integer of an erased type.
        #[doc(hidden)]
        pub union AnyInt { $($union_fields)*}
    };
    ([$int:ident : $nz:ident, $($tail:tt)*] [$($union_fields:tt)*] [$($counter:tt)*] ) => {
        #[allow(non_upper_case_globals)]
        const $int: usize =  {
            let h: &[()] = &[$($counter)*];
            h.len()
        };
        impl __NzDispatcherConstructor<$int> {
            #[must_use]
            pub const fn erase_with_id(self) -> (usize, AnyInt) {
                ($int, AnyInt {$int: self.0})
            }
        }
        impl __NzDispatcher<{$int}>  {
            #[must_use]
            pub const fn __new(expr: AnyInt) -> Self {
                Self(expr)
            }
            #[must_use]
            pub const fn __make_nz(self) -> $nz {
                let inner = unsafe { self.0.$int };
                match $nz::new(inner) {
                    Some(x) => x,
                    None => panic!("Attempt to construct a non-zero constant with a value of zero")
                }
            }
        }
        impl __NzType for __NzDispatcher<{$int}> {
            type Nz = $nz;
        }

        impl_nz!{[$($tail)*] [$int: $int, $($union_fields)*] [(), $($counter)*]}
    };
}

impl_nz! {
    [i8: NonZeroI8,
    u8: NonZeroU8,
    i16: NonZeroI16,
    u16: NonZeroU16,
    i32: NonZeroI32,
    u32: NonZeroU32,
    i64: NonZeroI64,
    u64: NonZeroU64,
    i128: NonZeroI128,
    u128: NonZeroU128,
    isize: NonZeroIsize,
    usize: NonZeroUsize,
    ]
    []
    []
}

#[macro_export]
macro_rules! nz {
    ($e:expr) => {
        {

            const __ERASED_ID_AND_EXPR: (usize, $crate::AnyInt) = $crate::__NzDispatcherConstructor::__new($e).erase_with_id();
            const __ID: usize = __ERASED_ID_AND_EXPR.0;
            const __ERASED: $crate::AnyInt = __ERASED_ID_AND_EXPR.1;
            const __EXPR: <$crate::__NzDispatcher<__ID> as $crate::__NzType>::Nz = $crate::__NzDispatcher::<__ID>::__new(__ERASED).__make_nz();
            __EXPR
        }
};
}
fn main() {
  let x = nz!(64u8);
  const FOO: usize = 10;
  let y = nz!(1 + FOO);
  // let zero = nz!(1u8 - 1); // compile time error
  let z = nz!(123i32 + 40 + FOO as i32);
  dbg!((x, y, z));
}

keen sparrowBOT
#
[src/main.rs:101] (x, y, z) = (
    64,
    11,
    173,
)```
mental hull
#

However, mine doesn't support inferring i32 as the default

#

I had another approach that didn't use a union, but it evaluated the inner expression twice

idle oak
#

My macro requires the unstable feature to make inference work in the way I describe in the opening message:

The difference with other crates that provide similar macros is that this can infer the type of the argument from the expected NonZero type,
e.g.: let _: NonZeroU8 = nz!(3); infers that 3 is a u8 because it's expected to return a NonZeroU8.

Your macro has the same limitation as other crates released before mine,
that it can't infer the type of the integer argument from the expected NonZerotype:

error[E0034]: multiple applicable items in scope
  --> src/main.rs:87:112
   |
87 |             const __ERASED_ID_AND_EXPR: (usize, $crate::AnyInt) = $crate::__NzDispatcherConstructor::__new($e).erase_with_id();
   |                                                                                                                ^^^^^^^^^^^^^ multiple `erase_with_id` found
...
96 |   let x: NonZeroU8 = nz!(64);
   |                      ------- in this macro invocation
   |
mental hull
#

i believe there might be a way to fix it. I'll try to do some playing around

#

i just wanted to show what i found through exploration because i found the concept interesting

idle oak
#

Sure, this is an approach I've used before to constify other things.
I didn't use it because I thought there were already enough macros for making NonZero with this limitation ferrisBut .

mental hull
#

ferrisBut indeed

#

i have a cursed idea now

#

wait no that wouldn't work

keen sparrowBOT
#
()```
mental hull
#

I have a cursed idea

#

and it actually works

#

just gotta make it into a macro

#

it abuses closure type inference along with impl Trait

#

oh wait

mental hull
#

i'm so close

keen sparrowBOT
#
5, 65535```
stark tartan
#

Assumes the zero value has all zero bytes and that the NonZero type has repr(transparent).

keen sparrowBOT
#
5, 65535```
mental hull
#

NonZero* is guranteed to have the same layout as the type. You can just make the NonZero trait unsafe

#

oh! I misinterpreted the assignment. I thought that the input always had to be const evaluated even if not used in a const context

mental hull
#

wait lemme read the inline const rfc again

idle oak
#

Hey, this isn't the "post your implementation of integer to NonZero conversion" thread,
please only post your implementation here if it works in all the cases shown in this example,
and always evaluates the argument at compile-time.
(feel free to ping me in some other thread/channel if you want me to see your implementation anyway)

mental hull
#

i have a feeling that it may not be possible without inline_const because of the way that rust's inference works