Rustにおける「recursion limit reached」エラーの解決方法

intermediate🦀 Rust2026-07-25| Rust (全バージョン), Cargo, 各種OS (Linux, macOS, Windows)

Error Message

error: recursion limit reached while expanding macro
#rust#マクロ#コンパイラエラー#再帰制限

エラーメッセージ

コーディングを進めていると、突然 Rust コンパイラが限界に達することがあります。これは通常、深くネストされたマクロ、複雑なトレイト解決、または大規模な状態マシンを扱っているときに発生します。多くの場合、以下のようなエラーが表示されます。

error: recursion limit reached while expanding macro
  | 
  = ヘルプ: クレートに #![recursion_limit = "256"] 属性を追加して、再帰制限を増やすことを検討してください

コンパイラがマクロではなく、特定の型を指し示すこともありますが、根本的な原因は同じです。

error: reached the recursion limit while instantiating `some_function::<...>`

原因

再帰制限は、安全装置(セーフティネット)だと考えてください。デフォルトでは、Rust コンパイラはマクロの展開や型の解決において、128段階の再帰で停止します。これにより、マクロが誤って自分自身を無限に呼び出し続け、システムの RAM をすべて使い果たしてコンパイラがハングアップするのを防いでいます。

最近の Rust では、この制限に達したからといって必ずしもコードが間違っているわけではありません。serdedieselyew といった強力なクレートは、高度なマクロマジックや複雑なジェネリクスを使用しています。これらは、通常の動作であってもコンパイラの128段階の制限を簡単に超えてしまうことがあります。

解決ステップ

1. クイック修正:クレートの制限値を引き上げる

ロジックに問題がなく、単にプロジェクトの規模が大きいだけの場合は、コンパイラの提案に従いましょう。この属性は、ルートファイル(通常は main.rs または lib.rs)の最上部に配置する必要があります。

インポート文や他のコードよりも前に、以下の行を挿入します。

#![recursion_limit = "256"]

256で足りない場合は、512を試してください。LeptosDioxus のような大規模なウェブフレームワークでは、1024が必要になることもあります。この制限を少し増やしてもコンパイル時間がわずかに増えるだけで、実行時のパフォーマンスには影響しません。

2. 再帰的マクロのリファクタリング

もし自分で書いたマクロが制限に達しているなら、「関数型」の再帰パターンを使用している可能性があります。これらのパターンはエレガントですが、コンパイラにとっては負荷がかかります。典型的な再帰的マクロは以下のようになります。

macro_rules! my_macro {
    ($head:expr) => {
        process($head);
    };
    ($head:expr, $($tail:expr),*) => {
        process($head);
        my_macro!($($tail),*); // 項目ごとに再帰の深さが+1される
    };
}

このマクロに130個の項目を渡すと、コンパイラのデフォルト制限を超えてクラッシュします。これは、Rust に組み込まれている反復演算子を使用して、項目を反復的に処理するように変更することで解決できます。

macro_rules! my_macro {
    ($($item:expr),*) => {
        $(
            process($item);
        )*
    };
}

反復バージョンは一段階で展開されます。項目が10個でも10,000個でも、再帰の深さは1のままです。

3. 複雑な型とトレイトの分離

複雑な async コードなどで型のインスタンス化中にエラーが発生する場合、単に制限を引き上げるのが唯一の解決策であることも多いです。しかし、制限値を2048以上に設定する必要がある場合は、型が複雑に絡み合いすぎている可能性があります。

トレイトオブジェクトBox<dyn Trait>)を使用することで、静的ディスパッチの長い連鎖を断ち切ることができます。コンパイラは dyn に遭遇すると、コンパイル時に具体的な型を解決するのを停止します。これにより、コンパイラが追跡すべき深さのカウンターが実質的にリセットされます。

修正の確認方法

- **状態をクリアする:** `cargo clean` を実行して、古いビルド成果物を削除します。
- **再ビルド:** `cargo check` を実行します。フルビルドよりも高速で、制限がまだ低いかどうかをすぐに確認できます。
- **場所を確認する:** エラーが変わらない場合は、`#![recursion_limit]` がエラーの発生している特定のクレートの `main.rs` に記述されているか再確認してください。ワークスペースのルートに置いても、サブクレートのエラーが解決されないことがあります。

トラブルを避けるためのヒント

- **手続き型マクロへの切り替え:** `macro_rules!` のロジックが手に負えなくなってきたら、`quote` クレートを使用した手続き型マクロの導入を検討してください。メンテナンスが容易で、同じ再帰の深さのロジックに依存しません。
- **Async のネストに注意:** 深くネストされた async ブロックやクロージャは、巨大で複雑な状態マシンを生成することがあります。これらを小さく名前の付いた関数に分割することで、コンパイラの負荷を軽減できます。
- **依存関係の確認:** 新しいライブラリを追加した後に突然このエラーが発生した場合は、そのライブラリのドキュメントを確認してください。動作させるために高い再帰制限を明示的に要求している場合があります。

Related Error Notes