Another approach to the nonzero macro

Initialization of a nonzero number, where assertion happens at compile time:

// Using macro_rules for performant compilation, tested to work in stable Rust
#[macro_export]
macro_rules! nonzero {
    ($n:expr) => {
        // This is in a const context
        const {
            // Constant assertion that works even when running `cargo check`
            const _: () = {
                if $n == 0 { ::core::panic!("Cannot be zero!") }
            };

            // SAFETY: Checked that $n is not zero
            unsafe { ::core::num::NonZero::new_unchecked($n) }
        }
    };
}

This macro accepts all numbers that can be wrapped inside a nonzero, and returns it wrapped in a nonzero struct, as long as that number is not zero. Else, an evaluation panic will happen at compile time.

This API allows following patterns:

const MY_NUM: i32 = 1234; // Some constant defined in an external crate
    
const MY_NUM_AS_NONZERO: core::num::NonZero<i32> = nonzero!(MY_NUM);
// This code panics at compile time
let zero = nonzero!(1u16 - 1);

// But this compiles
let non_zero = nonzero!(1u16 + 1);

Is this something that could be implemented into the standard library?

Note that you can already write the following:

const MY_NUM_AS_NONZERO: NonZero<i32> = match NonZero::new(MY_NUM) {
    Some(nz) => nz,
    None => panic!("Cannot be zero!"),
};

Since this is totally safe there's less of a reason to do this in the stdlib.

On the other hand I think it might be more interesting to have dedicated literals for NonZero, or if we ever get some kind of custom literal or literal macros we could use those.

1 Like

This is mostly an answer to how you can create a Vec using the vec! macro, but for NonZero numbers instead: By being short and concise. Though the vec! may only exist because it is not possible to do Vec::from([1, 2, 3, 4]) in a constant context.

It is mostly a way to act like a literal without being one: It is much easier to implement this macro to the standard library than to make an enhanced literal system.

And this macro depends on stable NonZero anyway, so if said literal system where to be stabilized, it will conflict with it the same way as the NonZero struct does.

Also this macro will never panic in a run-time context.

Option::unwrap and expect both work in const too.

4 Likes

You can do const { NonZero::new(123).unwrap() }, and while making it a macro will make that nicer, it'll need a strong justification to be in std.

3 Likes

I don't think so. Instead, takes_nonzero(2) should just work, same as how literals are already type-overloaded so that takes_u8(2) and takes_i128(2) both work.

6 Likes

Optimally you'd just write this:

const X: NonZero<i32> = 1234;

and similarly:

const X: BigInteger = 123123123123123123123123123123123123123123131;

This could work if there was a trait for integer literals and we had const traits.

7 Likes

I agree with both scott and tczajka. This is much better than using a macro.

Why does your macro nest const blocks inside each other? I don't see how that helps. I think your macro is just unnecessarily complicated.

The same is true for const { NonZero::new(123).unwrap() }. So you could replace your entire macro by

macro_rules! nonzero {
    ($n:expr) => {
        const {
            ::core::num::NonZero::new($n).unwrap()
        }
    };
}

That is so simple that it doesn't really need a macro.

1 Like

I wouldn't quite say it doesn't need a macro. For literals, occupying space in a larger call or construction exception, it's a lot of verbiage over a plain integer, and I'd rather have a macro for it until the plain integer works. Whether or not that macro needs to live in the stdlib is an additional question though.