Rustのクロージャの理解:Fn、FnMut、FnOnceの実用的な観察
Emily Parker
Product Engineer · Leapcell

Rustプログラミング言語において、クロージャは、匿名関数を定義し、周囲の環境から変数をキャプチャできる強力で柔軟な機能です。Rustのクロージャシステムは、Fn、FnMut、FnOnceという3つのコアトレイトによって定義されています。これらのトレイトは、クロージャがキャプチャされた変数とどのように相互作用するか、何回呼び出すことができるか、および環境を変更できるかどうかを決定します。これらのトレイトを理解することは、Rustのクロージャメカニズムを習得し、効率的で安全なコードを作成するために不可欠です。
この記事では、Fn、FnMut、FnOnceの3つのトレイトについて、定義、ユースケース、メソッド、適用可能なシナリオ、ベストプラクティスを詳細に紹介し、関連する概念を包括的に学習できるようにコード例を示します。
Fn、FnMut、FnOnceとは?
Fn、FnMut、FnOnceは、Rustの標準ライブラリで定義されている3つのトレイトであり、クロージャ(または呼び出し可能なオブジェクト)の動作を記述します。主な違いは、キャプチャされた変数へのアクセス方法と、呼び出し時の所有権ルールにあります。
- FnOnce: クロージャを1回だけ呼び出すことができることを示します。呼び出された後、クロージャ自体が消費され、二度と使用できなくなります。
- FnMut: クロージャを複数回呼び出すことができ、呼び出されたときにキャプチャされた変数を変更できることを示します。
- Fn: クロージャを複数回呼び出すことができ、キャプチャされた変数を変更せずに読み取るだけであることを示します。
これらの3つのトレイトの間には、継承関係があります。
- FnはFnMutから継承し、FnMutはFnOnceから継承します。
- したがって、クロージャがFnを実装している場合、FnMutとFnOnceも自動的に実装します。FnMutを実装している場合、FnOnceも実装します。
各トレイトの定義
FnOnce
FnOnce
トレイトは、次のシグネチャを持つcall_once
メソッドを定義します。
pub trait FnOnce<Args> { type Output; fn call_once(self, args: Args) -> Self::Output; }
- 特徴:
call_once
はself
(&self
または&mut self
の代わりに)を取ります。これは、クロージャが呼び出されると、それ自体の所有権が移転され、したがって1回だけ呼び出すことができることを意味します。 - ユースケース: クロージャがキャプチャされた変数を移動する必要がある場合、または1回限りの操作を実行する必要がある場合に適しています。
FnMut
FnMut
トレイトは、次のシグネチャを持つcall_mut
メソッドを定義します。
pub trait FnMut<Args>: FnOnce<Args> { fn call_mut(&mut self, args: Args) -> Self::Output; }
- 特徴:
call_mut
は&mut self
を取ります。これにより、クロージャは呼び出されたときに内部状態またはキャプチャされた変数を変更でき、複数回呼び出すことができます。 - ユースケース: クロージャが複数の呼び出しにわたって環境を変更する必要がある場合に適しています。
Fn
Fn
トレイトは、次のシグネチャを持つcall
メソッドを定義します。
pub trait Fn<Args>: FnMut<Args> { fn call(&self, args: Args) -> Self::Output; }
- 特徴:
call
は&self
を取ります。これは、クロージャがそれ自体とキャプチャされた変数を不変に借用するだけであることを意味します。環境を変更せずに複数回呼び出すことができます。 - ユースケース: クロージャを複数回呼び出す必要があり、データを読み取る場合にのみ適しています。
クロージャがこれらのトレイトを実装する方法
Rustのコンパイラは、クロージャがキャプチャされた変数をどのように使用するかに基づいて、どのトレイトを実装するかを自動的に決定します。クロージャは、次の3つの方法で変数をキャプチャできます。
- 値による(move): クロージャは変数の所有権を取得します。
- 可変参照(
&mut
): クロージャは変数への可変参照をキャプチャします。 - 不変参照(
&
): クロージャは変数への不変参照をキャプチャします。
実装されるトレイトは、キャプチャされた変数がどのように使用されるかによって異なります。
- FnOnceのみを実装する: クロージャはキャプチャされた変数を移動します。
- FnMutとFnOnceを実装する: クロージャはキャプチャされた変数を変更します。
- Fn、FnMut、およびFnOnceを実装する: クロージャはキャプチャされた変数を読み取るだけです。
コード例
FnOnceを実装するクロージャ
fn main() { let s = String::from("hello"); let closure = move || { drop(s); // Moves s and drops it }; closure(); // Called once // closure(); // Error: Closure has been consumed }
説明: クロージャはmoveでs
をキャプチャし、その所有権を取得して呼び出されたときにドロップします。s
が移動されるため、クロージャは1回だけ呼び出すことができ、FnOnce
のみを実装します。
FnMutを実装するクロージャ
fn main() { let mut s = String::from("hello"); let mut closure = || { s.push_str(" world"); // Modifies s }; closure(); // First call closure(); // Second call println!("{}", s); // Outputs "hello world world" }
説明: クロージャは可変参照でs
をキャプチャし、各呼び出しで変更します。環境を変更する必要があるため、FnMut
とFnOnce
を実装します。
Fnを実装するクロージャ
fn main() { let s = String::from("hello"); let closure = || { println!("{}", s); // Reads s without modification }; closure(); // First call closure(); // Second call }
説明: クロージャは不変参照でs
をキャプチャし、変更せずに読み取るだけです。したがって、Fn
、FnMut
、およびFnOnce
を実装します。
これらのトレイトを関数パラメータで使用する
クロージャを関数の引数として渡すことができ、関数はトレイト境界を使用して必要なクロージャの動作を指定する必要があります。
FnOnceの使用
fn call_once<F>(f: F) where F: FnOnce(), { f(); } fn main() { let s = String::from("hello"); call_once(move || { drop(s); }); }
説明: call_once
はFnOnce
クロージャを受け入れ、それを1回呼び出します。これにより、キャプチャされた変数を移動するクロージャに適しています。
FnMutの使用
fn call_mut<F>(mut f: F) where F: FnMut(), { f(); f(); } fn main() { let mut s = String::from("hello"); call_mut(|| { s.push_str(" world"); }); println!("{}", s); // Outputs "hello world world" }
説明: call_mut
はFnMut
クロージャを受け入れ、それを2回呼び出します。クロージャはキャプチャされた変数を変更できます。f
はmut
として宣言する必要があることに注意してください。
Fnの使用
fn call_fn<F>(f: F) where F: Fn(), { f(); f(); } fn main() { let s = String::from("hello"); call_fn(|| { println!("{}", s); }); }
説明: call_fn
はFn
クロージャを受け入れ、それを2回呼び出します。クロージャはキャプチャされた変数を読み取るだけです。
各トレイトをいつ使用するか?
適切なトレイトの選択は、クロージャに必要な動作によって異なります。
FnOnce
- ユースケース: クロージャが1回だけ呼び出されるか、キャプチャされた変数を移動する必要がある場合。
- 例: 所有権を移転する1回限りの操作。
FnMut
- ユースケース: クロージャを複数回呼び出す必要があり、キャプチャされた変数を変更する必要がある場合。
- 例: カウンターまたは状態の更新。
Fn
- ユースケース: クロージャを複数回呼び出す必要があり、キャプチャされた変数を読み取る場合にのみ適しています。
- 例: ロギングまたはデータクエリ。
関数を設計する場合、最も寛容なトレイトを選択すると、柔軟性が向上します。たとえば、FnOnce
はすべてのクロージャを受け入れますが、呼び出しが制限され、Fn
は複数回の呼び出しを許可しますが、不変性が必要です。
ベストプラクティス
- Fnを優先する: クロージャが変数を変更する必要がない場合は、最大限の互換性のために
Fn
を使用します。 - 変更が必要な場合はFnMutを使用する: クロージャが状態を更新する必要がある場合は、
FnMut
を選択します。 - 1回限りのクロージャにはFnOnceを使用する: クロージャが変数を移動するか、1回限りのタスクを実行する場合は、
FnOnce
を使用します。 - APIに適したトレイトを選択する: 1回限りの呼び出しには
FnOnce
を使用し、複数の読み取り専用呼び出しにはFn
を使用し、変更を伴う複数回の呼び出しにはFnMut
を使用します。 - ライフタイムに注意する: 借用エラーを回避するために、キャプチャされた変数が十分に長く存続することを確認してください。
RustプロジェクトのホスティングにはLeapcellをお選びください。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、またはRustで開発。
無制限のプロジェクトを無料でデプロイ
- 使用量に応じてのみ支払い。リクエストや料金は発生しません。
他に類を見ない費用対効果
- アイドル料金なしの従量課金制。
- 例:25ドルで平均応答時間60msで694万リクエストをサポート。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI / CDパイプラインとGitOps統合。
- 実行可能な洞察のためのリアルタイムのメトリックとロギング。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ。構築に集中するだけです。
詳細については、ドキュメントをご覧ください。
Xでフォローしてください:@LeapcellHQ