Failure within a Macro

Hi, I have a crate called nonempty-collections, within which is the following macro:

macro_rules! nev {
    () => {compile_error!("An NEVec cannot be empty")};
    ($h:expr, $( $x:expr ),* $(,)?) => {{
        let mut v = $crate::NEVec::new($h);
        $( v.push($x); )*
        v
    }};
    ($h:expr) => {
        $crate::NEVec::new($h)
    }
}

This allows the syntax nev![1,2,3] for creating an NEVec. The macro behaves identically to vec!, save that it also accounts for the empty case and proactively warns the user at compile time, as you can see with the compile_error! line. LSPs likewise add a red squiggle if you type nev![].

Now, we're attempting to extend this to support nev!["foo"; 3] syntax, which vec! also allows. This repeats the given element N times, so in this case something like ["foo", "foo", "foo"].

However, I'm having a hell of time trying to use compile_error! for this. The internet advertises that the addition of the following case should work:

    ($elem:expr; $n:expr) => {{
        match std::num::NonZeroUsize::try_from($n) {
            Ok(z) => $crate::vector::from_elem($elem, z),
            Err(_) => compile_error!("n cannot be 0"),
        }
    }};

No matter what value I pass to the macro as N, it triggers the n cannot be 0 message. More manual hacks this like also don't work:

        const VAL: usize = $n;

        if VAL > 0 {
            $crate::vector::from_elem($elem,  unsafe { std::num::NonZeroUsize::new_unchecked(VAL) })
        } else {
            compile_error!("n cannot be 0")
        }

Have I fundamentally misunderstood how compile_error! works?

1 Like

compile_error!() produces an error if it is compiled. The kind of conditional you can use with it is a #[cfg] or a macro, not an if. To check a value at compile time, you need a panic!() in a const context; but this will only work if the input value is a constant.

If you're okay with nev![] only working with constant expressions, then you can do something like this:

    ($elem:expr; $n:expr) => {{
        let n = const {
            let n = $n;
            assert!(n > 0);
            n
        };
        // build your vector here
    }};

This will produce a compilation error (in cargo build, but not necessarily cargo check) if n is not zero, but also if n is not a constant expression.

3 Likes

you should use panic!() in this case, compile_error!() is not "evaluated" like an expression, it triggers the diagnostic at type checking!

1 Like