Improving NonZero type ergonomics

There are two pain points I've experienced while using the generic std::num::NonZero type:

  1. The type doesn't implement the Default trait, forcing you to write custom Default impl for your structs and enums that otherwise only require a derive impl.
  2. No way to initialize the type without calling unwrap or using an unsafe block, even when the value is known at compile time.

The first problem can be addressed by implementing Default for all NonZero compatible types and setting the value to 1. If the value 1 isn't a reasonable default for the signed integers, then Default should at least be implemented for the unsigned types.

The second problem can be addressed by adding a new declarative macro to the standard library, which let's you initialize the type with a compile-time checked integer literal. The macro definition is extremely simple. Try it out on playground. Update: as pointed out by @FZs this macro is unsound. See @ogoffart's macro definition instead.

macro_rules! non_zero {
    (0) => {
        compile_error!("Value cannot be zero")
    };
    ($x: literal) => {
        unsafe { std::num::NonZero::new_unchecked($x) }
    };
}

Thoughts?

1 Like

Nit:

Your macro is unsound.

let zero = non_zero!(0u32);
let zero = non_zero!(00);
let zero = non_zero!(0_0);
let zero = non_zero!(0b0);
let zero = non_zero!(0x00_0000_0u8);

Playground

It should be a const fn, so that it checks the value, not the shape of the literal.

I otherwise like your proposal.

2 Likes

Well that's embarrassing. You're absolutely right. The macro is unsound. I suppose a macro like the one implemented in nonzero_ext crate would be appropriate.

macro_rules! non_zero {
    ($x: literal) => {
        const { std::num::NonZero::new($x).expect("cannot be zero") }
    };
}
7 Likes

This works!

What I really want is for 1 to just be a legal value for NonZero<u32> and such.

After all, 1 already uses type inference to be a legal u32 and i32 and u8 and i128 and …, so just let it be nonzero and pattern types too.

Bonus points for going through some kind of const trait so that 1 can be a legal my_custom::u24 and such too.

14 Likes

You know, now that we have a single NonZero type, nonzero literals would only require a single lang item :thinking:

Using a const trait is likely possible but more difficult. I tried it a couple years ago and ran into a fair amount of problems.

1 Like