Simplifying a `match` using a Rust macro - rust

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?

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

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

Related

How to use compile time constants to generate a function name in Rust? [duplicate]

Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.

Why can Rust not use const as literals? [duplicate]

Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.

Rust macro error: local ambiguity: multiple parsing options

The following rust code does not compile because of the macro error
error: local ambiguity: multiple parsing options: built-in NTs stmt ('s') or 1 other option.
macro A is fine. Macro B shows the error.
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $($s: stmt)*; $e: expr) => {
println!("hello");
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; 5];
}
This minimal reproducible example in B is exactly what I need. I want the macro to accept multiple let statements and terminate by some other expression.
What is the ambiguity that is being referred to?
Is there a way around it?
Of those tokens accepted after statement fragments I have tried several combinations yet none appear to make a difference. Neither does swapping the statement with a token tree.
Expressions are statements, so $($s: stmt)*; $e: expr is ambiguous because the compiler can't decide between using s or e when it encounters one.
Since you only expect bindings, you can easily expand them yourself:
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $(let $p:pat = $v:expr)*; $e: expr) => {
$(let $p = $v);*
println!("hello: {}", $e);
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; x+2];
}
Note that this doesn't support including types in the binding (let a: i32 = 42;) because pat can't be followed by :.

How do I relax the non-exhaustive patterns check for a nested match on known variants?

How do I persuade the Rust compiler that the internal match expression is fine here, as the outer match has already restricted the possible types?
enum Op {
LoadX,
LoadY,
Add,
}
fn test(o: Op) {
match o {
Op::LoadX | Op::LoadY => {
// do something common with them for code reuse:
print!("Loading ");
// do something specific to each case:
match o {
// now I know that `o` can only be LoadX | LoadY,
// but how to persuade the compiler?
Op::LoadX => print!("x"), /* LoadX specific */
Op::LoadY => print!("y"), /* LoadY specific */
_ => panic!("shouldn't happen!"),
}
println!("...");
}
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::LoadX);
test(Op::LoadY);
test(Op::Add);
}
I tried two approaches, but neither seems to work.
Name the or-pattern and then match using that name:
match o {
load#(Op::LoadX | Op::LoadY) => {
// ...
match load {
// ...
}
}
That's not valid Rust syntax.
Name and bind every constructor:
match o {
load#Op::LoadX | load#Op::LoadY => {
// ...
match load {
//...
}
}
That still doesn't satisfy the exhaustiveness check, hence the same error message:
error[E0004]: non-exhaustive patterns: `Add` not covered
--> src/main.rs:14:19
|
14 | match load {
| ^ pattern `Add` not covered
Is there any idiomatic way of solving this problem or should I just put panic!("shouldn't happen") all over the place or restructure the code?
Rust playground link
I think that you just need to refactor your code, obviously LoadX and LoadY are very close. So I think you should create a second enumeration that regroup them:
enum Op {
Load(State),
Add,
}
enum State {
X,
Y,
}
fn test(o: Op) {
match o {
Op::Load(state) => {
// do something common with them for code reuse
print!("Loading ");
// do something specific to each case:
match state {
State::X => print!("x"),
State::Y => print!("y"),
}
println!("...");
}
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::Load(State::X));
test(Op::Load(State::Y));
test(Op::Add);
}
This make more sense to me. I think this is a better way to express what you want.
You cannot. Conceptually, nothing prevents you from doing o = Op::Add between the outer match and the inner match. It's totally possible for the variant to change between the two matches.
I'd probably follow Stargateur's code, but if you didn't want to restructure your enum, remember that there are multiple techniques of abstraction in Rust. For example, functions are pretty good for reusing code, and closures (or traits) are good for customization of logic.
enum Op {
LoadX,
LoadY,
Add,
}
fn load<R>(f: impl FnOnce() -> R) {
print!("Loading ");
f();
println!("...");
}
fn test(o: Op) {
match o {
Op::LoadX => load(|| print!("x")),
Op::LoadY => load(|| print!("y")),
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::LoadX);
test(Op::LoadY);
test(Op::Add);
}
should I just put panic!("shouldn't happen")
You should use unreachable! instead of panic! as it's more semantically correct to the programmer.

Is there a way to use custom patterns such as a regex or functions in a match?

I'm writing a toy programming language in Rust. I prototyped the parser logic in Ruby:
def rd_tree(chars)
loop do
case c = chars.next
when /\s/
# whitespace stuff
when "("
# open paren stuff
when ")"
# close paren stuff
else
# default stuff
end
end
end
And now I'm converting it to Rust:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
if c.is_whitespace() {
// whitespace stuff
} else if c == '(' {
// open paren stuff
} else if c == ')' {
// close paren stuff
} else {
// default stuff
}
}
}
I resorted to using an if, else-if chain because as far as I can tell, Rust's match feature is limited to destructuring, enums, and type patterns. Is there a way to match on regexes or boolean functions? If not, is there a more idiomatic pattern here than if, else-if? I expect the logic to have more branches in the future and I want it to stay neat.
Not yet. The match patterns must be composed of things that can be statically verified by the compiler.
However, you can use a match guard:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
match c {
c if c.is_whitespace() => {}
'(' => {}
')' => {}
_ => {}
}
}
}
A match guard allows you to run a function against whatever the pattern matched.
In the future, constant evaluation may be improved to allow calling functions in place of a pattern:
#[derive(PartialEq, Eq)]
struct Foo {
f: usize,
g: usize,
}
impl Foo {
const fn repeated(x: usize) -> Self {
Foo { f: x, g: x }
}
}
fn main() {
let f = Foo { f: 0, g: 1 };
match f {
const { Foo::repeated(22) } => println!("hi"),
_ => println!("1"),
}
}
This work is tracked in issue #57240. RFC 2920 "const expressions and patterns" (and its tracking issue #76001) are also relevant.
It's not immediately obvious to me how this would work with your exact example or a regex without a substantial amount of effort though.

Resources