simonzack
simonzack

Reputation: 20928

Simplifying a `match` using a Rust macro

There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.

I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):

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.");
    },
  }
}

I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:

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

I'm rather stumped too how I can print the Question doesn't exist. as a default case.

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

The way I'd like to use it, is (or anything just as short is fine as well):

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

I read a bit of the Rust book, but there aren't exactly that many examples of macros.

How would I go about doing this?

Upvotes: 1

Views: 1488

Answers (2)

Jmb
Jmb

Reputation: 23264

The error message explained:

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

In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod

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

In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:

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

Although as pointed out by @prog-fh's answer, better to use a match in the macro, same as in the straight code:

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

Upvotes: 2

prog-fh
prog-fh

Reputation: 16850

Rather than many if statements, I just reproduced the match statement with a repetition $( ... )* for all the available branches. It seems to behave like the extensive match expression.

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.");
          }
        }
    };
}

Upvotes: 5

Related Questions