Rustにおけるマクロと関数：どちらをいつ使うべきか？

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/7936107b-a700-4506-8ad7-1ca29e936c7f.png)](https://leapcell.io/?lc_t=q_rustmacrofunc)

Rust の開発を行う際、よく「コードを簡潔にするためにマクロを使うべきか、それとも関数を使うべきか？」という疑問に直面します。

本記事では、マクロの使用シーンを分析し、どのような場面でマクロを活用すべきかを明確にします。まずは結論から述べましょう：

> マクロと関数は互いに代替するものではなく、補完関係にあります。それぞれ得意な場面が異なり、適切に使い分けることで優れた Rust コードを書くことができます。

では、具体的にマクロの使用シーンを見ていきましょう。

## マクロの分類

Rust のマクロには以下の種類があります：

- 宣言的マクロ（Declarative Macros, `macro_rules!`）
- 手続き的マクロ（Procedural Macros）

手続き的マクロはさらに次の 3 つに分類されます：

- カスタム派生マクロ（Custom Derive Macros）
- 属性マクロ（Attribute Macros）
- 関数風マクロ（Function-like Macros）

Rust において、関数とマクロはいずれもコードの再利用と抽象化に重要なツールです。関数は主にロジックをカプセル化し、既知の数や型の引数を処理し、型安全性と可読性を提供します。一方、マクロはコンパイル時にコードを生成し、関数では対応しきれないような場面（任意の数・型の引数の受け入れ、コード生成、メタプログラミングなど）に適しています。

## 具体的な使用シーンの分析

### 宣言的マクロ（`macro_rules!`）

#### シナリオ ①：可変数の引数や異なる型の引数を処理する

**問題点：**

- Rust の関数は定義時に引数の数と型を明示的に指定する必要があり、可変数の引数や異なる型の引数を直接受け取ることができません。
- `println!` のような機能を実現したいが、任意の数・型の引数を受け入れる仕組みが必要。

**マクロによる解決策：**

- 宣言的マクロはパターンマッチを利用して任意の数・型の引数を受け入れることが可能。
- `$()*` や `$var` などのメタ変数を使って、引数リストをキャプチャできる。

**例：**

```rust
// 可変長引数を受け取るマクロ
macro_rules! my_println {
    ($($arg:tt)*) => {
        println!($($arg)*);
    };
}

fn main() {
    my_println!("Hello, world!");
    my_println!("Number: {}", 42);
    my_println!("Multiple values: {}, {}, {}", 1, 2, 3);
}
```

**関数の制限：**

- 関数は任意の数・型の引数を受け取るシグネチャを定義できない。
- Rust の可変長引数を関数で扱うには `format_args!` などの特殊な仕組みが必要。

**マクロと関数の協調：**

- マクロは引数の収集と展開を担当し、最終的に関数（`println!` の場合 `std::io::stdout().write_fmt()`）を呼び出す。
- 関数は実際のロジックの実行を担当し、マクロはコード生成や引数解析を行う。

#### シナリオ ②：重複するコードパターンの簡素化

**問題点：**

- コード内に同じような処理が大量に存在する場合、手動で記述するとミスが発生しやすく、メンテナンスの負担も大きい。
- 特に、テストケースの生成やフィールドアクセサの実装など、パターン化されたコードが頻出する。

**マクロによる解決策：**

- 宣言的マクロを利用すると、パターンマッチを使って類似のコードを自動生成できる。
- マクロを使えば、手作業によるコード記述を削減し、ミスを防ぐことが可能。

**例：**

```rust
// 構造体に対して getter メソッドを自動生成するマクロ
macro_rules! generate_getters {
    ($struct_name:ident, $($field:ident),*) => {
        impl $struct_name {
            $(
                pub fn $field(&self) -> &str {
                    &self.$field
                }
            )*
        }
    };
}

struct Person {
    name: String,
    email: String,
}

generate_getters!(Person, name, email);

fn main() {
    let person = Person {
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    println!("Name: {}", person.name());
    println!("Email: {}", person.email());
}
```

**関数の制限：**

- 関数では、入力に応じて複数の関数を動的に生成することはできず、各フィールドごとに手動で getter を実装する必要がある。
- コンパイル時にコードを自動生成することは不可能で、メタプログラミング機能が不足している。

**マクロと関数の協調：**

- マクロを使ってコード生成を行い、関数を具体的な実装として利用する。
- 生成された関数を実際のコードで呼び出すことで、コードの可読性とメンテナンス性を向上させる。

#### シナリオ ③：小規模な埋め込み DSL（ドメイン特化言語）の実装

**問題点：**

- より自然な構文やドメイン特化の記述を Rust のコード内で直接使用し、可読性や表現力を向上させたい。
- Rust の標準的な文法では記述が冗長になりがちなケース（HTML や SQL のような構文を組み込みたい）。

**マクロによる解決策：**

- 宣言的マクロを使うと、特定の構文を解析し、対応する Rust コードを生成できる。
- パターンマッチと再帰的なマクロ展開を利用して、シンプルな DSL を構築可能。

**例：**

```rust
// 簡単な HTML DSL を定義するマクロ
macro_rules! html {
    ($tag:ident { $($inner:tt)* }) => {
        format!("<{tag}>{content}</{tag}>", tag=stringify!($tag), content=html!($($inner)*))
    };
    ($text:expr) => {
        $text.to_string()
    };
    ($($inner:tt)*) => {
        vec![$(html!($inner)),*].join("")
    };
}

fn main() {
    let page = html! {
        html {
            head {
                title { "My Page" }
            }
            body {
                h1 { "Welcome!" }
                p { "This is a simple HTML page." }
            }
        }
    };
    println!("{}", page);
}
```

**関数の制限：**

- 関数では、カスタム構文の解析ができず、通常の Rust の式しか扱えない。
- 関数のネストが深くなると、コードが冗長で直感的でなくなる。

**マクロと関数の協調：**

- マクロはカスタム構文の解析を担当し、それを Rust コードに変換する。
- 関数は文字列の整形や実行時処理を行う。

### 手続き的マクロ（Procedural Macros）

手続き的マクロは、Rust の抽象構文木（AST）を操作することで、より複雑なコード生成や変換を可能にする強力なマクロです。以下の 3 種類に分類されます：

- **カスタム派生マクロ（Custom Derive Macros）**
- **属性マクロ（Attribute Macros）**
- **関数風マクロ（Function-like Macros）**

#### カスタム派生マクロ（Custom Derive Macros）

シナリオ：特定のトレイトを型に自動実装する

**問題点：**

- Rust の型に対して同じトレイトの実装が必要な場合、手動で記述すると冗長でメンテナンスが面倒になる。
- 型のフィールドや属性に応じて、実装内容をカスタマイズしたい。

**マクロによる解決策：**

- カスタム派生マクロを利用すると、コンパイル時に型の定義を解析し、適切なコードを自動生成できる。
- 例えば、`serde` の `Serialize` や `Deserialize` などが典型的な例。

**例：**

```rust
// 必要なライブラリをインポート
use serde::{Serialize, Deserialize};

// #[derive] で Serialize / Deserialize を自動実装
#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
}

fn main() {
    let person = Person {
        name: "Alice".to_string(),
        age: 30,
    };

    // JSON 文字列にシリアライズ
    let json = serde_json::to_string(&person).unwrap();
    println!("Serialized: {}", json);

    // JSON からデシリアライズ
    let deserialized: Person = serde_json::from_str(&json).unwrap();
    println!("Deserialized: {} is {} years old.", deserialized.name, deserialized.age);
}
```

**関数の制限：**

- 関数では、型の定義に応じてトレイトの実装を自動生成できない。
- フィールドの情報に基づいたコード生成を行うことができない。

**マクロと関数の協調：**

- カスタム派生マクロがトレイト実装のコードを生成し、関数はその実装を利用する。

#### 属性マクロ（Attribute Macros）

シナリオ：関数や型の動作を修正する

**問題点：**

- 関数や型の振る舞いをコンパイル時に変更したい（例：ログの自動追加、パフォーマンス測定、コードのインジェクション）。
- 変更を簡潔な形で適用し、メンテナンスコストを下げたい。

**マクロによる解決策：**

- 属性マクロを利用すると、関数や型にアノテーションを付けるだけで、新しい振る舞いを追加できる。
- 例えば、実行前後にログを自動追加するような機能を実装可能。

**例：**

```rust
// 手続き的マクロを定義
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_execution(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let block = &input.block;

    let expanded = quote! {
        fn #fn_name() {
            println!("Entering function {}", stringify!(#fn_name));
            #block
            println!("Exiting function {}", stringify!(#fn_name));
        }
    };

    TokenStream::from(expanded)
}

// マクロを関数に適用
#[log_execution]
fn my_function() {
    println!("Function body");
}

fn main() {
    my_function();
}
```

**関数の制限：**

- 関数単体では、外部からの修正やコードのインジェクションができない。
- ログを手動で追加すると冗長になり、ミスのリスクが増える。

**マクロと関数の協調：**

- 属性マクロで関数の挙動を変更し、関数の本来のロジックはそのまま維持する。

#### 関数風マクロ（Function-like Macros）

シナリオ：カスタム構文やコード生成を行う

**問題点：**

- 特定の入力フォーマットを受け取り、対応するコードを生成したい（例：設定値の初期化、ルーティングの自動生成）。
- 実行時ではなくコンパイル時にコードを処理したい。

**マクロによる解決策：**

- 関数風マクロを利用すると、`TokenStream` を解析して適切な Rust コードを生成できる。
- 特定のフォーマットの入力を受け取り、カスタム処理を行うのに適している。

**例：**

```rust
// 文字列を大文字に変換する関数風マクロ
use proc_macro::TokenStream;
use quote::quote;

#[proc_macro]
pub fn make_uppercase(input: TokenStream) -> TokenStream {
    let s = input.to_string();
    let uppercased = s.to_uppercase();
    let output = quote! {
        #uppercased
    };
    TokenStream::from(output)
}

// マクロの使用
fn main() {
    let s = make_uppercase!("hello, world!");
    println!("{}", s); // 出力: HELLO, WORLD!
}
```

**関数の制限：**

- 関数ではコンパイル時に文字列を処理できず、実行時の変換が必要になる。
- 実行時の変換は余分なコストが発生するため、コンパイル時に処理する方が効率的なケースがある。

**マクロと関数の協調：**

- 関数風マクロはコンパイル時のコード生成を担当し、関数はその結果を利用してロジックを実行する。

## どのように選択すべきか？

### 基本的な指針

- **関数を優先**: 関数で実装できる場合は、コードの可読性と保守性を優先して関数を使うべき。
- **マクロは適切に使う**: 関数では対応できない場合のみマクロを活用するが、複雑になりすぎるとデバッグが難しくなるので注意。

### 関数が苦手でマクロが得意なシナリオ

- **可変数の引数を受け付ける必要がある**
- **コンパイル時のコード生成を行い、冗長性を削減する**
- **カスタム構文（DSL）を作成し、直感的な表現を可能にする**
- **トレイトの自動実装**
- **コードの構造や動作をコンパイル時に変更する**

### マクロが苦手で関数が得意なシナリオ

- **複雑なロジックの処理**：関数の方が直感的で書きやすい
- **型安全性とエラーチェック**：関数は明示的な型情報を持ち、コンパイラの型チェックが適用される
- **可読性と保守性**：関数の方が展開後のコードが明確で理解しやすい
- **デバッグとテスト**：関数はユニットテストやデバッグがしやすい

これらの指針を活用し、関数とマクロを適切に使い分けることで、より優れた Rust コードを実装できるようになります。

---

### [私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_rustmacrofunc)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/590ff7f7-6f59-4ff6-8625-bcfa7317dd27.png)](https://leapcell.io/?lc_t=q_rustmacrofunc)

[Leapcell](https://leapcell.io/?lc_t=q_rustmacrofunc)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/83cd9c89-7f20-4eac-b0a1-7ade299eba61.png)](https://leapcell.io/?lc_t=q_rustmacrofunc)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/macros-and-functions-in-rust-when-to-use)