Rustのクロージャの仕組み：Fn、FnMut、FnOnce

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/35f46f19-2d1f-41f3-b23e-e68c9f24d9ff.png)](https://leapcell.io/?lc_t=q_rustclosure)

Rust プログラミング言語において、クロージャ（closures）は非常に強力で柔軟な機能であり、無名関数を定義し、その環境内の変数をキャプチャすることができます。Rust のクロージャシステムは、**Fn、FnMut、FnOnce** という 3 つのコアトレイトによって定義されます。これらのトレイトは、クロージャがキャプチャした変数とどのように相互作用するか、何回呼び出せるか、環境を変更できるかを決定します。これらの理解は、Rust のクロージャメカニズムを習得し、効率的で安全なコードを書くために不可欠です。

本記事では、**Fn、FnMut、FnOnce** の 3 つのトレイトについて詳しく説明し、それぞれの定義、用途、使用方法、適用シナリオを紹介します。また、コード例やベストプラクティスを提供し、これらの知識を総合的に学べるようにします。

## Fn、FnMut、FnOnce とは？

**Fn、FnMut、FnOnce** は、Rust の標準ライブラリで定義されたトレイトであり、クロージャ（または任意の呼び出し可能なオブジェクト）の振る舞いを記述します。これらの主な違いは、クロージャがキャプチャした変数をどのように扱うか、および呼び出し時の所有権のルールにあります。

- **FnOnce**：クロージャは **一度だけ** 呼び出すことができ、呼び出し後に消費（consume）され、再利用できません。
- **FnMut**：クロージャは **複数回** 呼び出せ、キャプチャした変数を **変更** することができます。
- **Fn**：クロージャは **複数回** 呼び出せますが、キャプチャした変数を **変更せずに読み取る** だけです。

これらのトレイトは、以下のような **継承関係** を持ちます。

- **Fn** は **FnMut** を継承し、**FnMut** は **FnOnce** を継承します。
- つまり、あるクロージャが **Fn を実装しているならば、自動的に FnMut と FnOnce も実装されます**。
- 同様に、**FnMut を実装しているならば、自動的に FnOnce も実装されます**。

## 各トレイトの定義

### FnOnce

**FnOnce** トレイトは、次のような **`call_once`** メソッドを定義します。

```rust
pub trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}
```

- **特徴**：
  - `call_once` は `self` を受け取る（`&self` や `&mut self` ではない）。
  - これは **所有権がクロージャに移動する** ことを意味し、クロージャは **1 回だけ** 呼び出せる。
- **用途**：
  - キャプチャした変数を **移動（move）** したり、一度限りの操作を行うクロージャに適している。

### FnMut

**FnMut** トレイトは、次のような **`call_mut`** メソッドを定義します。

```rust
pub trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}
```

- **特徴**：
  - `call_mut` は **`&mut self`** を受け取るため、**クロージャは複数回呼び出すことができ、キャプチャした変数を変更できる**。
- **用途**：
  - キャプチャした環境の変数を変更するクロージャに適している。

### Fn

**Fn** トレイトは、次のような **`call`** メソッドを定義します。

```rust
pub trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}
```

- **特徴**：
  - `call` は **`&self`** を受け取るため、**クロージャは環境を変更せず、キャプチャした変数を単に読み取るだけ**。
  - **何度でも呼び出せる**。
- **用途**：
  - **キャプチャした変数を変更せずに、繰り返し呼び出すクロージャに適している**。

## クロージャがこれらのトレイトをどのように実装するか？

Rust のコンパイラは、クロージャがキャプチャした変数をどのように使用するかに基づいて、**自動的にどのトレイトを実装するかを決定します**。クロージャのキャプチャ方法には **3 つの種類** があります。

1. **値のキャプチャ（move）**：変数の所有権を取得する。
2. **可変参照のキャプチャ（&mut）**：変数の可変参照を取得する。
3. **不変参照のキャプチャ（&）**：変数の不変参照を取得する。

どのトレイトを実装するかは、変数の使用方法によって決まります。

- **FnOnce のみを実装**：キャプチャした変数を **所有権ごと移動** した場合。
- **FnMut と FnOnce を実装**：キャプチャした変数を **変更** した場合。
- **Fn、FnMut、FnOnce をすべて実装**：キャプチャした変数を **単に読み取るだけ** の場合。

### サンプルコード

#### FnOnce を実装するクロージャ

```rust
fn main() {
    let s = String::from("hello");
    let closure = move || {
        drop(s); // `s` をムーブして破棄
    };
    closure(); // 1回だけ呼び出せる
    // closure(); // エラー: クロージャはすでに消費された
}
```

**解析**：

- クロージャは **`move`** を使用して `s` の **所有権を取得** しました。
- 呼び出し時に `s` を `drop` してしまうため、このクロージャは **1 回しか呼び出せません**。
- **そのため、このクロージャは `FnOnce` のみを実装します**。

#### FnMut を実装するクロージャ

```rust
fn main() {
    let mut s = String::from("hello");
    let mut closure = || {
        s.push_str(" world"); // `s` を変更
    };
    closure(); // 1回目の呼び出し
    closure(); // 2回目の呼び出し
    println!("{}", s); // 出力: "hello world world"
}
```

**解析**：

- クロージャは `s` の **可変参照（&mut s）** をキャプチャしています。
- `s.push_str(" world")` により、キャプチャした変数を **変更** しています。
- **そのため、このクロージャは `FnMut` と `FnOnce` を実装します**。

#### Fn を実装するクロージャ

```rust
fn main() {
    let s = String::from("hello");
    let closure = || {
        println!("{}", s); // `s` をただ読むだけ
    };
    closure(); // 1回目の呼び出し
    closure(); // 2回目の呼び出し
}
```

**解析**：

- クロージャは `s` の **不変参照（&s）** をキャプチャしています。
- `s` を **変更せずに読み取るだけ** なので、何回でも呼び出すことができます。
- **このクロージャは `Fn`、`FnMut`、`FnOnce` のすべてを実装します**。

## 関数の引数としてトレイトを使用する

クロージャは **関数の引数** として渡すことができます。その場合、関数は **トレイト境界（trait bound）** を指定する必要があります。

### FnOnce を使用する関数

```rust
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 を使用する関数

```rust
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); // 出力: "hello world world"
}
```

**解析**：

- `call_mut` は `FnMut` を受け取るため、**複数回呼び出し可能**。
- **キャプチャした変数を変更できるクロージャに適した設計**。

### Fn を使用する関数

```rust
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` を受け取るため、**複数回呼び出し可能で、キャプチャした変数を変更しないクロージャ** を渡せます。

## どのトレイトを使うべきか？

適切なトレイトを選択するには、クロージャの振る舞いを考慮する必要があります。

### FnOnce を使うべき場合

- **シナリオ**：クロージャを **1 回しか呼び出さない**、または **キャプチャした変数の所有権を移動する必要がある**。
- **例**：一度だけ実行される処理や、所有権を別の場所に渡すケース。

### FnMut を使うべき場合

- **シナリオ**：クロージャを **複数回呼び出す** 必要があり、かつ **キャプチャした変数を変更する**。
- **例**：カウンターの更新や、状態を変更する処理。

### Fn を使うべき場合

- **シナリオ**：クロージャを **複数回呼び出し** たいが、キャプチャした変数を **変更しない**。
- **例**：ロギングやデータの取得処理。

### 関数設計の際の考え方

- **最も制約の少ないトレイトを選ぶ**：
  - **FnOnce は最も制約が厳しく**、1 回しか呼び出せない。
  - **FnMut は FnOnce より制約が少なく**、可変な状態を扱える。
  - **Fn は最も制約が少なく**、最も汎用的。
- **API 設計では、可能な限り制約の少ないトレイトを使う**：
  - `FnOnce` だと **すべてのクロージャを受け入れられるが、呼び出し回数が制限される**。
  - `Fn` を指定すると **変更を許可しないが、何度でも呼び出せる**。

## ベストプラクティス

1. **優先的に `Fn` を使用する**：

   - **キャプチャした変数を変更しないなら `Fn` を使う**。
   - `Fn` は **最も制約が少なく、最も汎用的** なので、互換性が高い。

2. **変更が必要なら `FnMut` を使用する**：

   - **キャプチャした変数を変更するなら `FnMut` を使う**。
   - 例：状態を更新するクロージャ。

3. **1 回だけの処理なら `FnOnce` を使用する**：

   - **クロージャが変数の所有権を取得する場合、`FnOnce` を使う**。
   - 例：データを移動する処理。

4. **API 設計では適切なトレイトを選ぶ**：

   - **最も制約が少ないトレイトを選ぶことで、柔軟な API を提供できる**。

5. **ライフタイムに注意する**：
   - キャプチャした変数のライフタイムが **クロージャの実行範囲をカバーしていることを確認する**。
   - そうしないと、借用エラーが発生する可能性がある。

---

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

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4b2fe04b-1c21-49cf-8adb-bde0357e3e7b.png)](https://leapcell.io/?lc_t=q_rustclosure)

[Leapcell](https://leapcell.io/?lc_t=q_rustclosure)は、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/e39c457a-48c0-49a7-90b5-8cdeed645a34.png)](https://leapcell.io/?lc_t=q_rustclosure)

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

---

[ブログでこの記事を読む](https://leapcell.io/blog/understanding-rust-closures)