Почему следующий макрос ожидает точку с запятой при вызове?

#rust #rust-macros

#Ржавчина #rust-макросы

Вопрос:

Я пытаюсь написать макрос для переключения между rayon par_iter и std iter в зависимости от функции сборки (возможно, забегая вперед, поскольку я еще мало читал о макросах). Здесь с макросом, кажется, немного лучше иметь дело, чем с функцией, поскольку для выполнения этой работы функции могут потребоваться некоторые относительно сложные типы; кроме того, макрос может оставаться более гибким в будущем, если я захочу добавить еще больше вариаций в функциях сборки, относящихся к тому, как запускать итераторы.

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

Я вижу следующую ошибку:

 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
 

Хотя я понятия не имею о первой ошибке, мне любопытно, почему ; ожидается a — как мне сделать его допустимым в контексте выражения?

Комментарии:

1. Я полагаю, вы не хотели комментировать #[cfg(not(feature = "threaded"))] ?

2. Извините, да, это была ошибка при экспериментировании. Исправлено.

Ответ №1:

По сути, это сводится к тому, что вам не разрешено иметь атрибуты в таких выражениях, например, что следующее недопустимо:

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

Чтобы устранить проблему, вы можете назначить их временной переменной, например:

Обратите внимание на двойное {{ и }} , которое приводит к блоку, так что конечным выражением является значение, которое приводит к блоку.

 #[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
    }};
}
 

Кроме того, вы также можете разделить его на два таких макроса:

 #[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()
    }
}
 

Комментарии:

1. Я попробовал первый, но у меня был только один { вместо двойного {{ ! Я думаю, это имеет смысл, поскольку внешний { должен находиться только в макросе.

2. Точно, с double {{ вы оборачиваете все в блок, а затем конечным it является результат блока. (Я обновил ответ, чтобы указать на это для будущих читателей.)