Rustのmacro_rules!でstructのselfを参照する方法

ハイサイ！オースティンやいびーん！

# 概要

Rustの`macro_rules!`を紹介し、`struct`の`impl`メソッドで使う`self`をmacroの中でどうやって参照できるか、二つの方法を紹介します！

# `macro_rules!`とは？

`macro_rules!`はRustの標準機能で、「コードを書いてくれるコード」を簡易的に書かせてくれる機能です。

コードを書くコード、つまり**メタプログラミング**なのです。

Rustにはいくつかのメタプログラミング手法がありますが、`macro_rules!`は最も広く使われている _宣言的_ メタプログラミングのツールです。

いくつかの制限があったりしますが、難しいと言われているメタプログラミングの中でも比較的手を出しやすいようになっています。

## メタプログラミング

メタプログラミングは無論、Rust特有の概念ではありません。実は、メタプログラミングは**80年代から**早くも流行した技法です。Lisp言語が流行らせた概念です。

簡単にいうと、つまりWikipedia通りにいうと、メタプログラミングの定義は、プログラムは他のプログラムを自分のデータとして扱えることです。とあるプログラムは、自分の中身を見て、新たに作ったり、変えたり、消したりできることです。

コードを作るコード、それがメタプログラミング。

## 簡単な例

では、簡単なmacroを書いてみれば以下のようになります。

```rust
macro_rules! print_ten_lns {
    ($s: expr) => {
        for _ in 0..10 {
            println!("{}", $s);
        }
    };
}

print_ten_lns!("Hello world!");
```

`macro_rules!`の後にmacro名になる`print_ten_lns`があり、その次に複数のパターンと展開後のコードを定義するところがあります。

`($hoge: ...)`とはmacroの入力となるパターンのことです。複数の変数を定義することができます。`$`を頭にしてMacroの変数名を定義して、コロン`:`の後にはどのようなMacro変数なのかを定義します。いくつかのMacro変数の種類がありますが、本記事では以下の二つの使います。

- expr: エクスプレッション。Rustだとほとんどのものがこれに該当する。`let hoge = ...`の`...`の部分です。
- ident: アイデンティファイヤー。`let hoge = "Hello hoge!";`の`hoge`に該当します。

太い矢印`=>`とカーリブレースに入っているのは、展開後のコードです。展開後とは、このMacroを使った時に、実際にどのようなRustコードになるか記載するところです。

さらに、任意の数の変数を入力して展開させることもできます。例えば、上記の例のように10回プリントするのではなく、任意の変数の数をプリントしたいとなったら以下のように書けます。

```rust
macro_rules! printlns {
    ($($s:expr),+ $(,)?) => {
        $(println!("{}", $s);)+
    };
}

printlns!("Hello world!", "Hello Japan!", "こんにちは", "你好");
```

パターンにキーワードは少し正規表現に似ています。

上記の繰り返しパターンが以下のようなコードに展開されます。

```rust
println!("{}", "Hello world!");
println!("{}", "Hello Japan!");
println!("{}", "こんにちは");
println!("{}", "你好");
```

Macroを使うことでさびた鉄でも柔軟性を出して、DRYを徹底できます。

`macro_rules!`についてきちんとした説明が以下の資料にあります！

https://danielkeep.github.io/tlborm/book/mbe-macro-rules.html

# `macro_rules!`で`self`を参照する

`macro_rules!`に制限があると上述しましたが、その一つは`self`の扱いです。その制限の中でメソッド内のmacroでselfをどうやって使えるのか見ていきましょう。

## `self`をmacroの引数として渡す

最初の方法は、`self`をメソッド内でmacroの引数として渡してあげるやり方です。`ident`型のmacro変数を使うと渡せます。

以下の例では、`Option<Vec<T>>`のプロパティをたくさん持っているパラメータの`struct`に、メソッドで`Option`内の配列に何かをプッシュするものです。ただし、`Option<Vec<T>>`が`None`の場合は、空の配列を入れた上で、プッシュするのです。

```rust
macro_rules! add_to_vec {
    ($self: ident, $prop: ident, $v_key: ident) => {{
        let vec = $self.$prop.get_or_insert(Vec::new());
        vec.push($v_key);

        $self
    }};
}
```

これを以下のようにメソッド内で使えます。

```rust
impl<'a> ParamBuilder<'a> {
// ...
    pub fn category_name(mut self, s: &'a str) -> Self {
        add_to_vec!(self, term_slug_and, s)
    }
// ...
}
```

これは以下のようなコードに展開されます。

```rust
pub fn category_name(mut self, s: &'a str) -> Self {
  let vec = self.term_slug_and.get_or_insert(Vec::new());
  vec.push(s);

  self
}
```

## メソッド内で`macro_rules!`を使ってmacroを定義する

一つのメソッドで複数回、似たようなロジックをコピペーして変数名、プロパティ名だけを変更するという経験はよくありますが、読者は如何でしょうか？

メソッド内で`macro_rules!`を使って定義すると、`self`を直接`macro`の展開文の中で使えます。

```rust
impl<'a> QueryBuilder<'a> {
// ...

    pub fn query(mut self) -> QueryAndValues {
        let params = self.params;

        macro_rules! add_if_some_id {
            ($prop: ident, $query: expr) => {
                if let Some(id) = params.$prop {
                    self.query.push_str($query);
                    self.values.push(Value::UInt(id));
                }
            };
        }

        add_if_some_id!(author, " AND post_author = ?");

        add_if_some_id!(p, " AND wp_post.ID = ?");
// ...
   }
}
```

このやり方だと、`self`も使えるし、`self`から抜いた`params`の変数も見えますので、わざわざ`$self`を渡す必要はないです！

# まとめ

`macro_rules!`を紹介して、`self`をmacroの中でどう扱うかも説明して参りましたが、如何でしょうか？

DRY（同じコードを繰り返すな）を徹底する上では非常に有用な`macro_rules!`です。筆者は`wp_query_rs`で紹介した二つのmacroで200行ほど短縮できました。

興味ある方はソースコードも公開しているので、ぜひご覧ください。

https://github.com/tronicboy1/wp_query_rs/blob/master/src/query_builder.rs

筆者は、The Bookの説明だけ読んで、Rustの`macro_rules!`が上級者向けで自分はなかなか使えないだろうと思っていましたが、Jon Gjengsetさんの**Crust of Rust**シリーズの[Crust of Rust: Declarative Macros](https://www.youtube.com/watch?v=q6paRBbLgNw)を見たら、案外使えるなと改めて思いました。

他の日本のRustaceanたちもいかがでしょうか？`macro_rules!`を現場で使えていますでしょうか？