Search code examples
rustrust-macros

Why does the following macro expect a semi-colon when called?


I'm trying to write a macro to switch between rayon's par_iter and std's iter depending on a build feature (possibly getting ahead of myself, as I've not read a lot on macros yet). A macro seems a bit better to deal with than a function here, since a function might need some relatively complex types to make this work; as well a macro might remain more flexible in the future if I wanted to add even more variations in build features pertaining to how to run iterators.

#[macro_export]
macro_rules! par_iter {
    ($($tokens:tt)*) => {
      #[cfg(feature = "threaded")]
      $($tokens)*.par_iter()
      #[cfg(not(feature = "threaded"))]
      $($tokens)*.iter()
    }
}

I see the following error:

error: macro expansion ignores token `b_slice` and any following
   --> src/util.rs:28:8                                                                      
    | 
28  |       $($tokens)*.iter();
    |        ^^^^^^^^^
    |                                                                                        
   ::: src/counting.rs:219:9                                                                 
    |
219 |         par_iter!(b_slice).map(WordCount::from)                                                                                                                                     
    |         ------------------- help: you might be missing a semicolon here: `;`
    |         |                                                                              
    |         caused by the macro expansion here
    |
    = note: the usage of `par_iter!` is likely invalid in expression context

While I have no idea about the first error, I'm curious why a ; is expected - how do I make it valid in expression context?


Solution

  • This essentially boils down to, that you aren't allowed to have attributes within expressions like that, e.g. that the following is invalid:

    b_slice.iter()
        #[cfg(not(feature = "threaded"))]
        .map(|x| x)
        .collect();
    

    To fix the issue, you can assign them to a temporary variable, like this:

    Note the double {{ and }}, which results in a block, such that the final expression is the value that block results in.

    #[macro_export]
    macro_rules! par_iter {
        ($($tokens:tt)*) => {{
            #[cfg(feature = "threaded")]
            let it = $($tokens)*.par_iter();
            #[cfg(not(feature = "threaded"))]
            let it = $($tokens)*.iter();
            it
        }};
    }
    

    Alternatively, you can also split it into two macros like this:

    #[cfg(feature = "threaded")]
    #[macro_export]
    macro_rules! par_iter {
        ($($tokens:tt)*) => {
            $($tokens)*.par_iter()
        }
    }
    
    #[cfg(not(feature = "threaded"))]
    #[macro_export]
    macro_rules! par_iter {
        ($($tokens:tt)*) => {
            $($tokens)*.iter()
        }
    }