Rustのマクロ: メタプログラミング
Rustのマクロシステムをマスターする:macro_rules!による宣言的マクロ、手続き型マクロ(カスタムDerive、属性風、関数風)。
Rust中級シリーズの最終回(第16回)。Rustにおける「マクロ」とは、メタプログラミング(コードを生成するコードを書く手法) を実現するための機能群を指します。
実行時に値を受け取る通常の関数と異なり、マクロはソースコードの構文トークンを受け取り、コンパイラが型検査やバイナリ生成を行う前にコードを展開します。
graph TD
A["Rustのマクロシステム"] --> B["宣言的マクロ (Declarative)<br/>macro_rules!"]
A --> C["手続き型マクロ (Procedural)<br/>TokenStreamを操作"]
C --> D["カスタムDerive<br/>#[derive(CustomTrait)]"]
C --> E["属性風 (Attribute-like)<br/>#[route(GET, '/')]"]
C --> F["関数風 (Function-like)<br/>sql!(...)"]
10歳でもわかる説明(ELI5):3Dコードプリンター#
関数(Function) と マクロ(Macro) の違い:
- 関数:ジューサーにオレンジを入れると、お店の営業中(実行時/Runtime)にフレッシュジュースを作ってくれる仕組みです。
- マクロ:工場がオープンする前(コンパイル時/Compile-time)に、3Dプリンターでジューサーそのものを組み立ててくれる仕組みです。
1. macro_rules! による宣言的マクロ#
Rustで最も広く使われているマクロです。match 式とよく似た構文パターンマッチングの仕組みを持っています:
標準ライブラリの vec! マクロをシンプルに再現した例:
src/main.rs
#[macro_export]
macro_rules! my_vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
fn main() {
let numbers = my_vec![1, 2, 3, 4, 5];
println!("自作ベクタ: {numbers:?}");
}rust構文のポイント:#
$x:expr:任意のRust式(Expression)にマッチし、$xにキャプチャします。$( ... ),*:カンマ区切りで0回以上の繰り返し(*)にマッチします。
2. 手続き型マクロ(Procedural Macros)#
宣言的マクロがパターンの置換であるのに対し、手続き型マクロはトークン列(TokenStream)を入力として受け取り、任意のRustコードを実行して新しい TokenStream を出力する関数です。
以下の3種類が存在します:
A. カスタム Derive マクロ#
#[derive(MyTrait)] でトレイトの実装コードを自動生成します:
// マクロ定義(専用の proc-macro クレート内)
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// ASTトークンを解析して impl HelloMacro for StructName を生成
}
// 利用側
#[derive(HelloMacro)]
struct Pancakes;rustB. 属性風(Attribute-like)マクロ#
構造体や関数などに付与するカスタム属性を作成します:
#[route(GET, "/users")]
fn get_users() {
// Webルーティングの定義
}rustC. 関数風(Function-like)マクロ#
関数呼び出しのように見えますが、独自の構文を解析してコードを生成できます:
let sql = sql!(SELECT * FROM users WHERE id = 1);rust関数とマクロの比較#
| 項目 | 関数 (Functions) | マクロ (Macros) |
|---|---|---|
| 実行フェーズ | 実行時 (Runtime) | コンパイル時 (Compile-time) |
| 引数の数 | 固定の型・引数の数 | 可変長引数(任意の数の引数を取れる) |
| 操作対象 | 実行時の値 | ソースコードの構文トークン (AST) |
| 複雑さ | シンプルで保守しやすい | 構文が複雑でデバッグが難解 |
まとめ#
- マクロは実行時オーバーヘッドなしにコンパイル時のコード自動生成を実現します。
macro_rules!は手軽に宣言的パターンマッチングを行える標準的な方法です。- 手続き型マクロはカスタムDerive、属性風、関数風を通じてRustの構文木を直接プログラミング可能にします。