Rustでマクロの引数の個数を数えてみようと思って以下のように書きました。
誤り
macro_rules! count_args {
($($elems: ident),*) => {
count_args!(0; $($elems),*)
};
($res: expr;) => {
$res
};
($res: expr; $elem: ident, $($rest: ident),*) => {
count_args!($res + 1; $($rest),*)
};
}
fn main() {
println!("{}", count_args!(x, y, z));
}
error: no rules expected the token `z`
--> src\main.rs:16:38
|
16 | println!("{}", count_args!(x, y, z));
|
z
で当てはまるルールがないと言われます。
理由
($res: expr; $elem: ident, $($rest: ident),*) => {
このルールでx, y, z
がx
でy
でz
と順番に消費されて$rest
が0個になると思い込んでました。しかし実際には、$elem: ident
の次にあるコンマまできっちりマッチングできるかどうか確かめられます。つまり、$elem: ident,
できっちり見られるのでx,
とy,
はマッチしますが、最後のz
は$elem: ident,
にマッチせず他に当てはまるルールがないのでエラーとなります。
ちなみに、count_args!
の呼び出しでz
の次にコンマを入れてcount_args!(x, y, z,)
にすると、この呼び出しの最後のコンマでunexpected end of macro invocation
と言われてエラーになります。
正答例
以下のように($res: expr;)
をコンマのない終端のトークンを受け入れられるように($res: expr; $elem: ident)
と修正するとうまくいきました。もちろん$res
も$res + 1
にしています。
macro_rules! count_args {
($($elems: ident),*) => {
count_args!(0; $($elems),*)
};
($res: expr; $elem: ident) => {
$res + 1
};
($res: expr; $elem: ident, $($rest: ident),*) => {
count_args!($res + 1; $($rest),*)
};
}
fn main() {
println!("{}", count_args!(x, y, z));
}
最後に
ルールにセミコロン使ってる時点で気づくべきだった。