Упрощение «соответствия» с помощью макроса Rust

#rust #macros

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

Вопрос:

Существует множество функций вопросов (сотни), и каждая из них может иметь свой тип. Для каждого вопроса я хочу запустить run_question функцию, которая показывает, сколько времени заняла эта функция, и распечатать ее вывод.

Я пытаюсь сократить следующее match выражение с помощью макроса Rust (запись run_question 100 раз делает код довольно длинным):

 fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
  let begin = Instant::now();
  let output: T = question_func();
  let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
  println!("{}", output);
  println!("{:.6}s taken", elapsed_secs);
}

fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }

fn main() {
  // ...
  match question_num {
    1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
    _ => {
      println!("Question doesn't exist.");
    },
  }
}
  

У меня нет опыта в написании макросов, и я попытался выполнить следующее, что не совсем работает. Это выдает ошибку:

 error: variable 'question_num' is still repeating at this depth
  

Я тоже в замешательстве, как я могу напечатать значение Question doesn't exist. по умолчанию.

 #[macro_export]
macro_rules! run_questions {
  ( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
    {
      if $chosen_question == $question_num {
        run_question($question_mod::solve);
      }
    }
  };
}
  

То, как я хотел бы его использовать, это (или что-нибудь столь же короткое, тоже подойдет):

 run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
  

Я немного прочитал книгу Rust, но там не так много примеров макросов.

Как бы я это сделал?

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

1. не могли бы вы рассмотреть другой вариант?

Ответ №1:

Вместо множества if операторов я просто воспроизвел match оператор с повторением $( ... )* для всех доступных ветвей. Похоже, он ведет себя как расширенное match выражение.

 macro_rules! run_questions {
    ( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
        match $chosen_question {
          $($question_num => run_question($question_mod),)*
          _ => {
                println!("Question doesn't exist.");
          }
        }
    };
}
  

Ответ №2:

В сообщении об ошибке объясняется:

 macro_rules! run_questions {
    ($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
  

В приведенном выше шаблоне у вас есть повторение с * оператором, который включает переменные $question_num и $question_mod

         if $chosen_question == $question_num {
            run_question($question_mod::solve);
        }
  

В соответствующем коде вы не можете использовать $question_num and $question_mod напрямую: поскольку они повторяются, они потенциально имеют более одного значения, и какое из них должен использовать компилятор здесь? Вместо этого вам нужно указать компилятору повторить блок кода, который использует эти переменные. Это делается путем окружения повторяющегося блока кода $() и добавления * оператора:

         $(if $chosen_question == $question_num {
            run_question($question_mod::solve);
        })*
  

Хотя, как указано в ответе @prog-fh, лучше использовать a match в макросе, так же, как и в прямом коде:

 match $chosen_question {
    $($question_num => run_question ($question_mod::solve),)*
    _ => println!("Question doesn't exist.")
};