Rust宣言マクロ 再帰テクニック


:::note
**本記事で伝えたいこと**

- 宣言マクロ内でマクロを呼ぶことが可能で、 **再帰的な呼び出しも可能**
- 再帰を使うことで、条件分岐のような複雑な繰り返しも可能
- しかしただでさえ遅いマクロがより低速になるデメリットがあるのでできるなら他の手段が吉

```rust:Rust
macro_rules! list {
    ( $v:expr ) => { // 停止するルール
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => { // 再帰呼び出しするルール
        Rc::new(Cons($v, list!($($rest),*) ))
    };
}
```
:::

こちらの記事は [Rustマクロ冬期講習アドベントカレンダー](https://qiita.com/advent-calendar/2024/rust-macro) 8日目の記事です！

> アドカレまとめ記事はこちら！: [Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)

宣言マクロを使えるようになるための詳細については[前々回の記事](https://qiita.com/namn1125/items/63756af0d3cecdaa02cd)で大体網羅しました。特に複雑なものはありません。

今回は知らなくても宣言マクロは使えるけど覚えておくと良さげな応用テクニック「宣言マクロの再帰呼び出し」を軽く紹介したいと思います！

# 今回の題材

https://doc.rust-jp.rs/book-ja/ch15-04-rc.html

TRPLのこの章ではスマートポインタの一つとして [`std::rc::Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) が紹介され、リスト構造を実現しています。

```rust:Rust
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}
```

ちょっとしたリストを作るたびに一々このように書いていたのでは面倒です。こういう時こそ宣言マクロの出番です！

```rust:Rust
macro_rules! list {
    // todo!
}

macro_rules! cons {
    // todo!
}

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = list![5, 10]; // Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))) に展開
    let b = cons!(3; a); // Rc::new(Cons(3, Rc::clone(&a))) に展開
    // ↓ ちょっと改変
    let c = cons!(4, 2; a); // Rc::new(Cons(4, Rc::new(Cons(2, Rc::clone(&a))))) に展開
}
```

今回のような入れ子構造に対しては繰り返し `$(...)*` で記述することが難しいです。逆に再帰だととても書きやすいので、再帰で書いてみます！

# 再帰でマクロ実装

まず `list` マクロから作ってみます。

`[1, 2, 3]` というリストを `list![1, list![2, list![3]]]` のような感じに見て考えます。一回のマクロ呼び出しで、`list![1, 2, 3, ...]` を `Rc::new(Cons(1, list![2, 3, ...]))` という形に変形したいので、そのように書きます！

```rust:Rust
macro_rules! list {
    ( $v:expr, $($rest:expr),* ) => { // 再帰呼び出しするルール
        Rc::new(Cons($v, list!($($rest),*) )) // 残りの部分をlistマクロに渡す
    };
}
```

ただこれだと `list![3]` みたいに最後の一つになった時の条件がないので、そのルールを始めに持っていきます！ `Nil` を入れると良さそうですね

```rust:Rust
macro_rules! list {
    ( $v:expr ) => { // 停止するルール
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => { // 再帰呼び出しするルール
        Rc::new(Cons($v, list!($($rest),*) ))
    };
}
```

次に `cons` マクロを作ってみます。

`cons!(a, b ; 追加先リスト)` という風に書くと、 `Rc::new(Cons(a, Rc::new(Cons(b, Rc::clone(&追加先リスト)))))` になるように書きたいです。先ほどと似た感じで、追加する値が一つになるまで再帰呼び出しするように書けばおkです！

```rust:Rust
macro_rules! cons {
    ($v:expr; $list:expr) => {
        Rc::new(Cons($v, Rc::clone(&$list)))
    };
    ($v:expr, $($rest_v:expr),*; $list:expr) => {
        Rc::new(Cons($v, cons!($($rest_v),*; $list)))
    };
}
```

全体はこんな感じになりました

```rust:Rust
macro_rules! list {
    ( $v:expr ) => {
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => {
        Rc::new(Cons($v, list!($($rest),*) ))
    };
}

macro_rules! cons {
    ($v:expr; $list:expr) => {
        Rc::new(Cons($v, Rc::clone(&$list)))
    };
    ($v:expr, $($rest_v:expr),*; $list:expr) => {
        Rc::new(Cons($v, cons!($($rest_v),*; $list)))
    };
}

#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    let a = list![5, 10];
    let b = cons!(3; a);
    let c = cons!(4, 2; a);

    dbg!(a, b, c);
}
```

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=58cf39729eaf7414e5dc304d729fb183

## パターンをまとめる

別れたままでも良いのですが、大して複雑でもないので `list` マクロと `cons` マクロを統合してしまいたいと思います！

```rust:Rust
macro_rules! list {
    ( $v:expr ) => {
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => {
        Rc::new(Cons($v, list!($($rest),*) ))
    };
    // consマクロからコピペ
    ($v:expr; $list:expr) => {
        Rc::new(Cons($v, Rc::clone(&$list)))
    };
    ($v:expr, $($rest_v:expr),*; $list:expr) => {
        Rc::new(Cons($v, list!($($rest_v),*; $list))) // consマクロではなくlistマクロを呼ぶようにしただけ
    };
}

#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    let a = list![5, 10];
    let b = list![3; a];
    let c = list![4, 2; a];

    dbg!(a, b, c);
}
```

`;` の有無で場合分けされるのでコピペするだけで難なく動きました！

# まとめ・所感

リスト構造をマクロで楽に作るという例でマクロの再帰呼び出しテクニックを紹介しましたが、そもそも題材が再帰的な構造を持つものだったので恣意的な例だったかもしれません。

ともかく、 `$(...)*` による繰り返し記述方法の他に、再帰的に書くことでも繰り返し表現が可能なことを伝えられたかなと思います。

ただ、再帰パターンは下手に書くと(例えば2変数で再帰するなど)マクロの展開をかなり遅くしてしまう可能性があるので、あまり乱用しない方が良いでしょう。

参考: https://veykril.github.io/tlborm/decl-macros/patterns/tt-muncher.html#performance