第19章: マクロ - コードを生成するコード
学習目標
- マクロとは何か、なぜ必要なのかを理解する
- 宣言的マクロ(
macro_rules!)の書き方を習得する - 手続き的マクロの3種類を知る
- マクロ展開の仕組みとデバッグ方法を学ぶ
- 実践的なDSL(ドメイン特化言語)を作成する
---
19.1 マクロの必要性と基礎概念
19.1.1 マクロとは何か
マクロ(Macro)は、コードを生成するコードです。コンパイル時に展開され、実際のRustコードに変換されます。
┌─────────────────────────────────────────────────────────┐
│ マクロ vs 関数の違い │
├─────────────────────────────────────────────────────────┤
│ │
│ 【関数】 【マクロ】 │
│ │
│ ✓ 実行時に呼ばれる ✓ コンパイル時に展開 │
│ ✓ 型が固定 ✓ 任意の数の引数 │
│ ✓ 引数は式のみ ✓ 式、文、型も受け取れる │
│ ✗ 可変長引数は難しい ✓ 可変長引数が簡単 │
│ ✗ 新しい構文を作れない ✓ DSLを作れる │
│ │
└─────────────────────────────────────────────────────────┘
19.1.2 既に使っているマクロ
実は、あなたはすでに多くのマクロを使っています:
// すべてマクロ!(末尾の!が目印)
println!("Hello, world!");
vec![1, 2, 3];
panic!("エラー発生");
assert_eq!(a, b);
関数ではなくマクロである理由:
// println!は可変長引数が必要
println!("値: {}", x); // 2引数
println!("値: {}, {}", x, y); // 3引数
println!("値: {}, {}, {}", x, y, z); // 4引数
// vec!は任意個の要素を受け取る
vec![1]; // 1要素
vec![1, 2]; // 2要素
vec![1, 2, 3, 4, 5]; // 5要素
---
19.2 宣言的マクロ(macro_rules!)
19.2.1 基本構文
宣言的マクロはパターンマッチングに基づいています:
macro_rules! マクロ名 {
(パターン1) => {
展開コード1
};
(パターン2) => {
展開コード2
};
}
最もシンプルな例:
macro_rules! say_hello {
() => {
println!("Hello, world!");
};
}
// 使用
say_hello!(); // => println!("Hello, world!");
19.2.2 引数を受け取るマクロ
式を受け取る:
macro_rules! double {
($x:expr) => {
$x * 2
};
}
fn main() {
let result = double!(5); // => 5 * 2 = 10
let result = double!(2 + 3); // => (2 + 3) * 2 = 10
println!("{}", result);
}
メタ変数の種類:
| 指定子 | マッチするもの | 例 |
|---|---|---|
| `expr` | 式 | `1 + 2`, `func()` |
| `stmt` | 文 | `let x = 5;` |
| `ty` | 型 | `i32`, `Vec |
| `ident` | 識別子 | `foo`, `bar` |
| `path` | パス | `std::vec::Vec` |
| `literal` | リテラル | `42`, `"hello"` |
| `block` | ブロック | `{ ... }` |
| `item` | アイテム | `fn f() {}`, `struct S;` |
| `pat` | パターン | `Some(x)` |
19.2.3 複数パターンのマッチング
macro_rules! create_function {
// パターン1: 引数なし
($func_name:ident) => {
fn $func_name() {
println!("{}が呼ばれました", stringify!($func_name));
}
};
// パターン2: 引数あり
($func_name:ident, $arg:ident : $arg_ty:ty) => {
fn $func_name($arg: $arg_ty) {
println!("{}({})が呼ばれました",
stringify!($func_name), $arg);
}
};
}
// 使用
create_function!(foo);
create_function!(bar, x: i32);
fn main() {
foo(); // => "fooが呼ばれました"
bar(42); // => "bar(42)が呼ばれました"
}
19.2.4 繰り返しパターン
可変長引数を処理する:
macro_rules! vec_literal {
// 繰り返し: $($x:expr),*
// $(...),* は「カンマ区切りで0回以上繰り返し」
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
fn main() {
let v = vec_literal![1, 2, 3, 4, 5];
println!("{:?}", v); // [1, 2, 3, 4, 5]
}
繰り返しの構文:
$(...) : 0回以上繰り返し$(...)+ : 1回以上繰り返し$(...), : カンマ区切りで0回以上$(...),+ : カンマ区切りで1回以上$(...) ; : セミコロン区切り---
19.3 実践的なマクロの例
19.3.1 HashMap初期化マクロ
macro_rules! hashmap {
// 空のマップ
() => {
std::collections::HashMap::new()
};
// キー:値のペアを受け取る
($($key:expr => $val:expr),* $(,)?) => {
{
let mut map = std::collections::HashMap::new();
$(
map.insert($key, $val);
)*
map
}
};
}
use std::collections::HashMap;
fn main() {
// 空のマップ
let empty: HashMap<i32, i32> = hashmap!();
// 初期値付き
let scores = hashmap! {
"Alice" => 95,
"Bob" => 87,
"Charlie" => 92,
};
println!("{:?}", scores);
}
ポイント:
$(,)?: 末尾のカンマをオプションにする- ブロック
{ ... }で囲むことで、一時変数がスコープ外に漏れない
19.3.2 計測マクロ
macro_rules! measure {
($name:expr, $block:block) => {
{
let start = std::time::Instant::now();
let result = $block;
let duration = start.elapsed();
println!("{}: {:?}", $name, duration);
result
}
};
}
fn main() {
let result = measure!("重い計算", {
let mut sum = 0;
for i in 0..1_000_000 {
sum += i;
}
sum
});
println!("結果: {}", result);
}
19.3.3 テストケース生成マクロ
macro_rules! test_cases {
($test_name:ident: $func:ident($($input:expr),*) => $expected:expr) => {
#[test]
fn $test_name() {
assert_eq!($func($($input),*), $expected);
}
};
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
// テストケースを生成
test_cases!(test_add_positive: add(2, 3) => 5);
test_cases!(test_add_negative: add(-1, -2) => -3);
test_cases!(test_add_zero: add(0, 0) => 0);
#[cfg(test)]
mod tests {
use super::*;
// 上記のマクロがこのようなコードに展開される:
// #[test]
// fn test_add_positive() {
// assert_eq!(add(2, 3), 5);
// }
}
---
19.4 マクロのデバッグとトレース
19.4.1 マクロ展開の確認
# マクロ展開後のコードを確認
cargo rustc -- -Z unstable-options --pretty=expanded
# またはcargo-expandを使用
cargo install cargo-expand
cargo expand
19.4.2 デバッグマクロ
// stringify!: 式をそのまま文字列に
macro_rules! debug_print {
($val:expr) => {
println!("{} = {:?}", stringify!($val), $val);
};
}
fn main() {
let x = 42;
debug_print!(x); // => "x = 42"
debug_print!(x + 10); // => "x + 10 = 52"
}
便利な組み込みマクロ:
// file!(), line!(), column!(): ソースコード位置
macro_rules! log {
($msg:expr) => {
println!("[{}:{}] {}", file!(), line!(), $msg);
};
}
fn main() {
log!("デバッグメッセージ");
// => "[src/main.rs:10] デバッグメッセージ"
}
---
19.5 手続き的マクロ概要
19.5.1 手続き的マクロの3種類
手続き的マクロはRustのコードで実装されるマクロです:
┌─────────────────────────────────────────────────────────┐
│ 手続き的マクロの3種類 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. カスタム derive マクロ │
│ #[derive(MyTrait)] │
│ struct MyStruct { ... } │
│ │
│ 2. 属性マクロ │
│ #[my_attribute] │
│ fn my_function() { ... } │
│ │
│ 3. 関数風マクロ │
│ my_macro!(...) │
│ │
└─────────────────────────────────────────────────────────┘
19.5.2 カスタム derive マクロの例
目標:#[derive(Builder)]を実装する
// 使用例(理想形)
#[derive(Builder)]
struct User {
name: String,
age: u32,
email: Option<String>,
}
// これが自動生成される:
// impl User {
// fn builder() -> UserBuilder { ... }
// }
//
// struct UserBuilder {
// name: Option<String>,
// age: Option<u32>,
// email: Option<String>,
// }
//
// impl UserBuilder {
// fn name(mut self, name: String) -> Self { ... }
// fn age(mut self, age: u32) -> Self { ... }
// fn email(mut self, email: String) -> Self { ... }
// fn build(self) -> Result<User, String> { ... }
// }
fn main() {
let user = User::builder()
.name("Alice".to_string())
.age(30)
.build()
.unwrap();
}
実装の骨組み(詳細は後の章で):
// Cargo.toml
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = "2.0"
// quote = "1.0"
// proc-macro2 = "1.0"
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// ここでコード生成ロジックを実装
let expanded = quote! {
impl #name {
pub fn builder() -> #name Builder {
// ...
}
}
};
TokenStream::from(expanded)
}
19.5.3 属性マクロの例
目標:関数の実行時間を計測する属性
// 使用例
#[measure_time]
fn heavy_computation() {
// 重い処理
}
// これが展開される:
// fn heavy_computation() {
// let _start = std::time::Instant::now();
// // 元の処理
// println!("実行時間: {:?}", _start.elapsed());
// }
19.5.4 関数風マクロの例
目標:SQL風のクエリDSL
// 使用例
let users = sql! {
SELECT name, age FROM users WHERE age > 18
};
// これがRustコードに変換される
---
19.6 実践的なDSL設計
19.6.1 コンフィグDSL
目標:設定ファイルを記述するDSL
macro_rules! config {
(
$(
$section:ident {
$(
$key:ident = $value:expr
),* $(,)?
}
)*
) => {
{
use std::collections::HashMap;
let mut config = HashMap::new();
$(
let mut section = HashMap::new();
$(
section.insert(
stringify!($key).to_string(),
$value.to_string()
);
)*
config.insert(stringify!($section).to_string(), section);
)*
config
}
};
}
fn main() {
let settings = config! {
database {
host = "localhost",
port = 5432,
name = "mydb",
}
server {
host = "0.0.0.0",
port = 8080,
}
};
println!("{:#?}", settings);
}
19.6.2 HTML DSL
macro_rules! html {
// 空タグ: <br/>
($tag:ident /) => {
format!("<{}/>", stringify!($tag))
};
// 内容あり: <p>text</p>
($tag:ident { $($content:tt)* }) => {
format!("<{}>{}</{}>",
stringify!($tag),
html!($($content)*),
stringify!($tag))
};
// テキストノード
($text:expr) => {
format!("{}", $text)
};
// 複数要素
($($element:tt)*) => {
{
let mut result = String::new();
$(
result.push_str(&html!($element));
)*
result
}
};
}
fn main() {
let page = html! {
html {
head {
title { "My Page" }
}
body {
h1 { "Welcome" }
p { "Hello, world!" }
br /
}
}
};
println!("{}", page);
}
19.6.3 状態機械DSL
macro_rules! state_machine {
(
initial: $initial:ident,
states: {
$(
$state:ident => {
$(
$event:ident -> $next:ident
),* $(,)?
}
),* $(,)?
}
) => {
#[derive(Debug, Clone, Copy, PartialEq)]
enum State {
$($state,)*
}
#[derive(Debug, Clone, Copy)]
enum Event {
$($($event,)*)*
}
impl State {
fn transition(self, event: Event) -> Option<State> {
match (self, event) {
$(
$(
(State::$state, Event::$event) => Some(State::$next),
)*
)*
_ => None,
}
}
fn initial() -> State {
State::$initial
}
}
};
}
// 使用例:信号機
state_machine! {
initial: Red,
states: {
Red => {
TimerExpired -> Green,
},
Green => {
TimerExpired -> Yellow,
},
Yellow => {
TimerExpired -> Red,
},
}
}
fn main() {
let mut state = State::initial();
println!("初期状態: {:?}", state);
state = state.transition(Event::TimerExpired).unwrap();
println!("次の状態: {:?}", state);
}
---
19.7 マクロのベストプラクティス
19.7.1 マクロを使うべきとき
使うべき場合:
- DSLの実装
- 可変長引数
println!, vec!のように任意個の引数が必要- コンパイル時計算
避けるべき場合:
- 普通の関数で十分
- デバッグが困難
- IDEサポート
19.7.2 読みやすいマクロの書き方
// ❌ 悪い例:読みにくい
macro_rules! m {
($x:expr,$y:expr) => {$x+$y};
}
// ✅ 良い例:フォーマット、コメント付き
macro_rules! add {
// 2つの式を加算
($x:expr, $y:expr) => {
$x + $y
};
}
ドキュメントコメント:
/// 2つの値を加算するマクロ
///
/// # Examples
///
///
/// let result = add!(2, 3);
/// assert_eq!(result, 5);
/// macro_rules! add {
($x:expr, $y:expr) => {
$x + $y
};
}
19.7.3 エラーハンドリング
macro_rules! safe_divide {
($numerator:expr, $denominator:expr) => {
{
let denom = $denominator;
if denom == 0 {
panic!("0で除算しようとしました");
}
$numerator / denom
}
};
}
fn main() {
let result = safe_divide!(10, 2); // OK: 5
// let result = safe_divide!(10, 0); // panic!
}
---
19.8 マクロの高度なテクニック
19.8.1 TTマンチング(Token Tree Munching)
再帰的なマクロ展開:
macro_rules! count {
// ベースケース:要素がない
() => {
0
};
// 再帰ケース:1つ消費して再帰
($head:expr $(, $tail:expr)*) => {
1 + count!($($tail),*)
};
}
fn main() {
let n = count!(1, 2, 3, 4, 5);
println!("要素数: {}", n); // 5
}
19.8.2 内部ルール
macro_rules! complex_macro {
// 公開インターフェース
(public $x:expr) => {
complex_macro!(@internal $x, 42)
};
// 内部実装(@で始めるのが慣習)
(@internal $x:expr, $default:expr) => {
$x + $default
};
}
19.8.3 マクロのエクスポート
// lib.rs
#[macro_export]
macro_rules! my_macro {
() => {
println!("exported macro!");
};
}
// 別クレートから使用
use my_crate::my_macro;
my_macro!();
---
19.9 まとめ
学んだこと
- マクロの基礎
macro_rules! の構文- パターンマッチング
$x:expr, $t:tyなど)
- 繰り返し($(...))- 実践的なマクロ
- 手続き的マクロ
- DSL設計
次のステップ
次の章では、Rustのテストシステムを詳しく学びます:
- 単体テスト
- 統合テスト
- ドキュメントテスト
- テスト駆動開発
---
参考資料
公式ドキュメント
追加リソース
- syn crate - 手続き的マクロ用パーサ
- quote crate - コード生成
- proc-macro-workshop - 実践演習