LoginSignup
3
1

More than 5 years have passed since last update.

Rustのマクロでハマってしまった単純な一例

Posted at

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, zxyzと順番に消費されて$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));
}

最後に

ルールにセミコロン使ってる時点で気づくべきだった。

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1