Use "compile_error!" in const if

I have a trait like this:

trait Foo {
    const NON_ZERO: usize;
}

I want the compiler to check if NON_ZERO is indeed a non-zero value before I use it, so I write like this:

fn Bar<T: Foo>() {
    if T::NON_ZERO == 0 {
        compile_error!("NON_ZERO is 0!")
    }
}

but this apparently not working, since compiler error will report as long as it exists.

Anyway I can check NON_ZERO at compile time?

You can const_assert_ne!() it, but consider to make it NonZeroUsize instead.

3 Likes

NonZeroUsize seems not working in this case, if I write like this:

const ITEMS: std::num::NonZeroUsize = unsafe {NonZeroUsize::new_unchecked(0)};

the compiler will not report error.

And if I write like this:

const ITEMS: std::num::NonZeroUsize = NonZeroUsize::new(0).unwrap();

compiler will report that:
``Option::::unwrap is not yet stable as a const fn

unsafe {} block is a marker to force compiler to trust whatever you says. It's up to you to not trigger any UB, otherwise the compiler would generate garbage binary like the ones you can get from C/++ compiler.

But yeah, I forgot the panic in const feature is not stabilized. Good news is it seems to be merged soonβ„’.

const_assert_ne seems not working either:

fn Bar<T: Foo>() {
        const_assert_ne!(T::NON_ZERO, 0);
}

the complier will report that:

error[E0401]: can't use generic parameters from outer function
 --> src/main.rs:9:26
  |
8 | fn Bar<T: Foo>() {
  |        - type parameter from outer function
9 |         const_assert_ne!(T::NON_ZERO, 0);
  |                          ^^^^^^^^^^^ use of generic parameter from outer function

I can only do like this:

    const NON_ZERO: usize = 0;
}

const_assert_ne!(u32::NON_ZERO, 0);

but there's no way I check every type which implements from trait Foo

Interestingly, it does catch it here:

const U8_NON_ZERO: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(0) };

impl Foo for u8 {
    const NON_ZERO: NonZeroUsize = U8_NON_ZERO;
}
1 Like

You can use this to make a macro constructor that will check it for you:

macro_rules! to_nonzero {
    ($value:expr) => {{
        const VALUE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked($value) };
        VALUE
    }};
}

impl Foo for u8 {
    const NON_ZERO: NonZeroUsize = to_nonzero!(0);
}

I'm worried that is this feature expected or some bug which shouldn't be rely on?

I wouldn't worry too much about it, but if you want you can do something like this:

macro_rules! to_nonzero {
    ($value:expr) => {{
        const VALUE: NonZeroUsize = {
            let value: usize = $value;
            
            let arr: [usize; 1] = [value];
            let idx = if value == 0 { 1 } else { 0 };
            
            unsafe {
                NonZeroUsize::new_unchecked(arr[idx])
            }
        };
        VALUE
    }};
}

impl Foo for u8 {
    const NON_ZERO: NonZeroUsize = to_nonzero!(0);
}

It will fail at compile time with an out-of-bounds error.

1 Like

That's an acceptable solution to me, although the error message remind me of the C++ template message hell :smile:

Unfortunately, we can't yet panic in constants.

Digging around this, for an associated constant it will error when used:

pub trait Foo {
    const NON_ZERO: NonZeroUsize;
}
impl Foo for u8 {
    const NON_ZERO: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(0) };
}
fn main() {
    let non_zero = u8::NON_ZERO;
}

Which doesn't seems consistent with normal constants :sweat_smile:

There seems to already be an issue about this:

1 Like

I'd use division by zero here, to improve the error message a bit (it will mention 0 / zero rather than 1):

macro_rules! non_zero {
    // Bonus branch to further improve the error message in the easy case
    (
        0 $(,)?
    ) => (
        compile_error!("Non-zero value expected")
    );

    (
        $value:expr $(,)?
    ) => ({
        const __VALUE__: ::core::primitive::usize = $value;
        #[deny(const_err)]
        const _: ::core::primitive::usize = __VALUE__ / __VALUE__;
        unsafe {
            ::core::num::NonZeroUsize::new_unchecked(__VALUE__)
        }
    });
} pub(in crate) use non_zero;

You can even push stuff further and start using helper traits and const bool-dispatching to generate an error message rather nice :slightly_smiling_face::

error[E0277]: the trait bound `Const<0_usize>: IsNonZero` is not satisfied
  --> src/lib.rs:16:40
   |
16 |         const NON_ZERO: NonZeroUsize = non_zero!(0);
   |                                        ^^^^^^^^^^^^ the trait `IsNonZero` is not implemented for `Const<0_usize>`
2 Likes

The const ub rfc explicitly defined that const ub being detected as error is not guaranteed and may change at any time.

https://rust-lang.github.io/rfcs/3016-const-ub.html

However, this is just a snapshot of what rustc currently does. None of this is guaranteed , and rustc may relax or otherwise change its UB checking any time.

2 Likes

An alternative that doesn't require unsafe code:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=46b2012284594b1335730bd404320b00

macro_rules! to_nonzero {
    ($value:expr) => {{
        const VALUE: NonZeroUsize = {
            let value = $value;
            match NonZeroUsize::new(value) {
                Some(x) => x,
                None => [/* expected non-zero value */][value],
            }
        };
        VALUE
    }};
}
error: any use of this value will cause an error
  --> src/main.rs:7:22
   |
3  | /         const VALUE: ::core::num::NonZeroUsize = {
4  | |             let value = $value;
5  | |             match ::core::num::NonZeroUsize::new(value) {
6  | |                 ::core::option::Option::Some(x) => x,
7  | |                 _ => [/* expected non-zero value */][value],
   | |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ index out of bounds: the length is 0 but the index is 0
8  | |             }
9  | |         };
   | |__________-
...
15 |       to_nonzero!(0);
   |       --------------- in this macro invocation
   |
   = note: `#[deny(const_err)]` on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
   = note: this error originates in the macro `to_nonzero` (in Nightly builds, run with -Z macro-backtrace for more info)
4 Likes