In the following code, I really don't find a way to make the macro rule args!
compile, without changing its syntax like used in the main function:
use std::rc::Rc;
trait Checker: Fn(i32) -> bool {}
impl<F> Checker for F where F: Fn(i32) -> bool {}
fn is_odd(n: i32) -> bool {
n % 2 == 1
}
fn is_positive(n: i32) -> bool {
n >= 0
}
fn and(a: impl Checker, b: impl Checker) -> impl Checker {
move |n: i32| a(n) && b(n)
}
macro_rules! args {
( $($name:ident : $checker:expr),* $(,)? ) => {{
let args: Vec<(String, Option<Rc<dyn Checker>>)> = vec![
$( args!(__arg stringify!($name).to_string(), $checker) ),*
];
args
}};
// LINE 26: the following rule is never matched:
(__arg $name:expr, None) => {
($name, None)
};
(__arg $name:expr, $checker:expr) => {
(
$name,
// LINE 33: can't give a type for argument `c` (this is a fn type):
$checker.map(|c| {
let c: Rc<dyn Checker> = Rc::new(c);
c
})
)
};
}
fn main() {
let _args = args![
a: Some(is_odd),
b: Some(and(is_positive, is_odd)),
c: None, // <-- This compiles well if this line is commented
];
}
This gives this error:
error[E0282]: type annotations needed
--> src/main.rs:33:27
|
33 | $checker.map(|c| {
| ^ consider giving this closure parameter a type
...
42 | let _args = args![
| _________________-
43 | | a: Some(is_odd),
44 | | b: Some(and(is_positive, is_odd)),
45 | | c: None, // <-- This compiles well if this line is commented
46 | | ];
| |_____- in this macro invocation
The problem is that None.map(|c| ...)
(line 33) needs a type for c
; but I can't give one because it is unnameable and it changes everytime.
So I tried to add a rule in the macro, specialized for None
(line 26); but the rule never matches : I guess it is because in the first, main rule of the macro, $checker
matches as an expr
, but the pattern None
is certainly considered as a token tree.
The only workaround I found is to make the final syntax more cumbersome by adding square brackets around each arg definition and change the main rule of the macro like this (but I would like to avoid it):
( $( [ $name:ident : $($checker:tt)* ] ),* $(,)? ) => ...
How can I correct this macro rule in order to make it work?
Unfortunately, in order to get the (__arg $name:expr, None)
rule to match, you must match your $checker
s as tt
s, ident
s or lifetime
s (tt
s being the only one that will work in this case). This can be cumbersome at times, as you found out yourself.
How about a simpler (for the user, at least) macro syntax?
macro_rules! args {
( $($name:ident $(: $checker:expr)?),* $(,)? ) => {{
let args: Vec<(String, Option<Rc<dyn Checker>>)> = vec![
$( args!(__arg stringify!($name).to_string() $(, $checker)?) ),*
];
args
}};
(__arg $name:expr) => {
($name, None)
};
(__arg $name:expr, $checker:expr) => {
(
$name,
{
let c: Rc<dyn Checker> = Rc::new($checker);
Some(c)
}
)
};
}
Which can be called in your example as
let _args = args![
a: is_odd,
b: and(is_positive, is_odd),
c,
];
I realize this does not directly fix your macro, but if the only reason you are using an Option
in the macro arguments is to specify the presence or absence of a Checker
, this may be a good (and arguably cleaner) alternative.