Search code examples
rustmacrosassertion

How to define custom asserts that can enabled/disabled depending on a constant?


Some asserts are more computationally expensive than others, and I would like to have a way to enable/disable certain asserts. The asserts would be enabled/disabled depending on a compile-time constant. Conceptually this should be possible through the use of macros to simply omit code, but how can I do this in a clean way in Rust? (see below for my attempt that 'works' but seems messy)

For the sake of an example, I want to have code like this:

const MY_ASSERT_LEVEL = 1;

fn do_something_complicated(a: u32, b: u32) -> bool{
   false
}

let a = 5;
let b = 10;
assert_simple!(a != b);
assert_heavy!(do_something_complicated(a, b));

I want to have assert_heavy if MY_ASSERT_LEVEL is at least 2, and similarly I want assert_simple if MY_ASSERT_LEVEL is at least 1. For the case MY_ASSERT_LEVEL == 0, I want both asserts to be disabled.

My current solution is as follows:

I define in main.rs const MY_ASSERT_LEVEL: u32 = 2; and then I write in a separate file my_asserts.rs:

#[macro_export]
macro_rules! assert_simple {
    ($($arg:tt)*) => {
        if $crate::MY_ASSERT_LEVEL >= 1 {
            assert!($($arg)*);
        }
    };

macro_rules! assert_heavy {
    ($($arg:tt)*) => {
        if $crate::MY_ASSERT_LEVEL >= 2 {
            assert!($($arg)*);
        }
    };

}

And this 'works', but it seems messy for several reasons:

  1. MY_ASSERT_LEVEL is defined in the main file rather than in the same file as the assert macros. I tried moving MY_ASSERT_LEVEL into the macro file, but the compiler tells me that the const MY_ASSERT_LEVEL exists but is inaccessible to the macros.
  2. I am exposing a global variable (const MY ASSERT_LEVEL) which has no role other than to define the macro levels. I would like to avoid this if possible.
  3. When using the macros in other modules, I need to manually include each macro by writing e.g., use crate::assert_simple. Since in my use case I may have multiple assert macros, instead I would like to use some blanked statements like use my_assert_macros::* but not sure how to get this functionality.

I suspect I do not quite understand the macro syntax in Rust.

Can someone please help me? How I can implement the asserts I would like in a clean Rust way? I tried searching online to understand macros but this is as far as I could get.


Solution

  • This is easily done with the right file/module structure. Set your project up like this:

    src
    ├─── main.rs
    └─── my_asserts.rs
    

    my_asserts.rs

    pub(crate) const MY_ASSERT_LEVEL: u32 = 1;
    
    macro_rules! assert_simple {
            ($($arg:tt)*) => {
                if $crate::my_asserts::MY_ASSERT_LEVEL >= 1 {
                    assert!($($arg)*);
                }
            };
        }
    
    macro_rules! assert_heavy {
            ($($arg:tt)*) => {
                if $crate::my_asserts::MY_ASSERT_LEVEL >= 2 {
                    assert!($($arg)*);
                }
            };
        }
    
    // these re-exports have to go after the macro definitions
    pub(crate) use assert_heavy;
    pub(crate) use assert_simple;
    

    main.rs

    use my_asserts::*;
    
    mod my_asserts;
    
    fn main() {
        assert_simple!(true);
        // this won't panic
        assert_heavy!(false);
    }