TL;DR
- Rust のマクロは、パースされたトークン列や抽象構文木(AST)に対して動作する。コードの構造を認識できるので、変数の捕捉などマクロにありがちな問題を避けられる。
- マクロには宣言的マクロと手続き的マクロの 2 種類があり、手続き的マクロはさらに 3 種類に分かれる。
内容
項目28 分別をもってマクロを使おうのうち、Rust のマクロの仕組みの話である。
他の言語のマクロとの違い
Rust のマクロは、他の言語のマクロと違い、トークン列や AST を扱う。そのため、コードの構造を認識でき、マクロ内の変数が呼び出し側の変数を捕捉してしまうような問題を避けられる。このような性質を持つマクロを健全なマクロと呼ぶ。
2 種類のマクロ
宣言的マクロは、マクロへの入力に応じて、プログラムに任意のコードを挿入する。println! がその例である。書き方は The Rust Reference の Macros By Example にある。
手続き的マクロは、パースされたトークンに応じて、プログラムに任意のコードを挿入する。derive マクロがその例である。動的型付け言語のような表現力を得つつ、コンパイル時の安全性は保たれる。また、リフレクションの制限による不便さを和らげることもできる。
手続き的マクロは、使う側とは別の独立したクレートとして定義する必要がある。そのクレートでは Cargo.toml に proc-macro = true を設定する。入力のトークン列を処理するには、標準のツールチェーンに含まれる proc_macro や、proc-macro2 を使う。
手続き的マクロは、さらに次の 3 種類に分かれる。
- 関数的マクロ: 引数を渡して呼び出す。
- 属性マクロ: プログラムの何らかの構文要素に付ける。
- derive マクロ: データ構造の定義に付ける。