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?
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.
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.
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.
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.
FWIW .unwrap()/.expect() do raise "syntactical alarm" bells which need to be soothed after the fact by spotting/pointing out the surrounding const context, which entails the error would be at compile-time, rather than at runtime (where tests could potentially miss the panic path).
Granted, to palliate that the macro can very much be defined by the user as part of some utils battery; but until we get something in the language or stdlib, be it a macro, or some "magical from-literal through const traits" construction, these NonZero<…> types do feel rather second-class citizens, with higher usability hindrance than bare integers. I won't say which one would be the best solution here (between macros, some const-generic ctor[1], or whatnot), but only that we acknowledge that there is some (mild) problem at play, here
Let's however note that an argument for a macro or built-in sugar for NonZeroliterals (i.e., not allowing the OP's use case with MY_NUM) could have the advantage of, by virtue of being a syntactical check, being able to be a true compile-check-time assertion, so cargo check/clippy would catch these, rather than a post-mono error with all the limitations it has (notably, a post-mono-error based API does not seem suitable for the stdlib standard of quality, wherein cargo check and cargo build might disagree w.r.t. the success of compilation).
Though perhaps this overarching question would be more generally handled when dealing with pattern types, I don't know
impl NonZeroUsize {
// "Compile-error" (codegen error) if `N == 0`.
const fn new_const<const N: usize>() -> Self {
const { Self::new(N).expect("expected the parameter to be non-zero") }
}
}
// example:
let two = NonZeroUsize::new_const::<2>();