![## Rust クロージャの理解
クロージャは、周囲の環境から変数を捕捉する匿名関数です。
型安全性を保ちつつ、コールバックやインラインロジックを書きやすくします。
### 主な概念
- **キャプチャモード**
- `&T`:不変参照で借用(`Fn`)
- `&mut T`:可変参照で借用(`FnMut`)
- `T`:所有権を移動(`FnOnce`)
- **実装されるトレイト**
- `Fn`:変更なしに複数回呼び出せる。
- `FnMut`:可変状態の変更が許容される。
- `FnOnce`:捕捉した値を消費し、1 回だけ使用できる。
### 構文
```rust
let x = 5;
let closure = |y: i32| x + y; // `x` を不変参照で捕捉
```
- パラメータと戻り値の型は推論されます。
- 明示的に型を書くこともできますが、必須ではありません。
### よく使われる場面
- **イテレータ** – `map`・`filter`・`fold` など。
- **コールバック** – イベント処理や非同期タスク。
- **遅延評価** – 必要になってから計算を行う。
### パフォーマンスの考慮点
- クロージャはゼロコスト抽象化であり、可能な限りインラインコードに変換されます。
- 大きな構造体を参照で捕捉すると不要なコピーを避けられます。
### 例:ベクタのフィルタリング
```rust
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = numbers.into_iter()
.filter(|n| n % 2 == 0)
.collect();
```
クロージャ `|n| n % 2 == 0` は何も捕捉せず、`FnMut` を実装しています。
### 要約
- クロージャを使えば簡潔で再利用可能なコードを書けます。
- キャプチャモードを理解することで正しい所有権の挙動が保証されます。
- 適切に `Fn`・`FnMut`・`FnOnce` を選択すればパフォーマンスも最適化できます。
クロージャをマスターすれば、Rust で関数型スタイルのプログラミングを強力に行うことが可能になります。](/_next/image?url=%2Fscreenshots%2F2026-01-25%2F1769298707825.webp&w=3840&q=75)
2026/01/25 3:42
## Rust クロージャの理解 クロージャは、周囲の環境から変数を捕捉する匿名関数です。 型安全性を保ちつつ、コールバックやインラインロジックを書きやすくします。 ### 主な概念 - **キャプチャモード** - `&T`:不変参照で借用(`Fn`) - `&mut T`:可変参照で借用(`FnMut`) - `T`:所有権を移動(`FnOnce`) - **実装されるトレイト** - `Fn`:変更なしに複数回呼び出せる。 - `FnMut`:可変状態の変更が許容される。 - `FnOnce`:捕捉した値を消費し、1 回だけ使用できる。 ### 構文 ```rust let x = 5; let closure = |y: i32| x + y; // `x` を不変参照で捕捉 ``` - パラメータと戻り値の型は推論されます。 - 明示的に型を書くこともできますが、必須ではありません。 ### よく使われる場面 - **イテレータ** – `map`・`filter`・`fold` など。 - **コールバック** – イベント処理や非同期タスク。 - **遅延評価** – 必要になってから計算を行う。 ### パフォーマンスの考慮点 - クロージャはゼロコスト抽象化であり、可能な限りインラインコードに変換されます。 - 大きな構造体を参照で捕捉すると不要なコピーを避けられます。 ### 例:ベクタのフィルタリング ```rust let numbers = vec![1, 2, 3, 4, 5]; let evens: Vec<i32> = numbers.into_iter() .filter(|n| n % 2 == 0) .collect(); ``` クロージャ `|n| n % 2 == 0` は何も捕捉せず、`FnMut` を実装しています。 ### 要約 - クロージャを使えば簡潔で再利用可能なコードを書けます。 - キャプチャモードを理解することで正しい所有権の挙動が保証されます。 - 適切に `Fn`・`FnMut`・`FnOnce` を選択すればパフォーマンスも最適化できます。 クロージャをマスターすれば、Rust で関数型スタイルのプログラミングを強力に行うことが可能になります。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Rust のクロージャは、
構文で書かれた軽量な匿名関数です。明示的に型を指定することも、コンパイラが推論させることもできます。クロージャが環境から変数をキャプチャするときは、そのキャプチャ方法を決定します:|args|
- 値で – 所有権をクロージャへ移動し、元の変数は使えなくなります。この場合クロージャは
を実装します。FnOnce- 可変参照で (
) – クロージャ内で変更が可能になり、&mut Tが実装されます。FnMut- 不変参照で (
) – 単に借用するだけで、&Tが実装されます。Fn
コンパイラはこれらのキャプチャモードに応じて自動的に適切なトレイト(、FnOnce、またはFnMut)を実装します。関数は変数をキャプチャできないため、通常の関数で外部変数を使用しようとするとエラー E0434 が発生します。Fn
Nightly 機能(とfn_traits)を使えば、クロージャを手動で分解して同じトレイトを実装する構造体に変換でき、挙動を明示的に制御できます。unboxed_closuresキーワードは所有権キャプチャを強制しますが、実装されるトレイトには影響しません。スレッド生成やクロージャを返す際にmoveライフタイムを満たすために必要です。'static
例では各キャプチャモードを示し、それらが型推論、トレイト選択、およびコンパイル診断に与える影響を説明しています。参照には Rust 本のクロージャ章、リファレンスガイド、明示的キャプチャに関するブログ記事、そしてとfn_traitsを扱う不安定版 Rust 本が含まれます。unboxed_closures
もし元の要約をそのまま残したい場合は、上記の欠落している箇条書きを追加して完全性を確保してください。
本文
閉包(Closure)の基礎
既に知っていると思いますが、Rust で閉包は次のような構文で書く関数です。
let double_closure = |x| x * 2; assert_eq!(4, double_closure(2));
通常の関数として書くとこうなります。
fn double_function(x: u32) -> u32 { x * 2 } assert_eq!(4, double_function(2));
ほぼ同じです。違いは
double_function の引数と戻り値の型が明示的に u32 と書かれている点だけです。閉包では型を省略したので、デフォルト整数型(
i32)が採用されました。以下で型を指定できます。
let double_typed_closure = |x: u32| -> u32 { x * 2 }; assert_eq!(4, double_typed_closure(2)); assert_eq!(4, double_typed_closure(2u32)); // assert_eq!(4, double_typed_closure(2u16)); // ← エラーになります。
閉包を使う典型例として
Option::map を挙げてみます。
assert_eq!(Some(4), Some(2).map(|x| x * 2)); assert_eq!(Some(4), Some(2).map(double_closure)); // 上で定義した double_closure assert_eq!(Some(4), Some(2).map(double_function)); // 関数も同様に使えます。
つまり、閉包は型推論を利用して関数を書きやすくした構文糖です。
変数の取得(Capture)
閉包と関数の主な違いは、閉包がその環境から変数を取得できる点にあります。関数はそうではありません。
let hello = "Hello "; let greeter_closure = |x| String::new() + hello + x; assert_eq!("Hello world", greeter_closure("world")); assert_eq!( Some("Hello world".to_owned()), Some("world").map(greeter_closure) );
greeter_closure の中で hello を使っているのが分かります。同じことを関数でやろうとするとエラーになります。
let hello = "Hello "; fn greeter_function(x: &str) -> String { String::new() + hello + x }
コンパイラは次のように言います。
error[E0434]: can't capture dynamic environment in a fn item --> src/main.rs:7:25 | 7 | String::new() + hello + x | ^^^^^ | = help: use the `|| { ... }` closure form instead
共有参照での取得
上記の
greeter_closure では、hello は読み取り専用だったので共有参照(&'a str)として取得されます。閉包宣言後も hello を使うことができます。
let hello = "Hello "; let greeter_closure = |x| String::new() + hello + x; // まだ使用可能 assert_eq!("Hello ", hello); assert_eq!("Hello world", greeter_closure("world")); // 再び assert_eq!("Hello ", hello);
可変参照での取得
可変参照を使って閉包が捕捉した値を書き換えることもできます。
let mut total = 0; let add_mut_closure = |x| total += x; // ここでは `total` にアクセスできません // assert_eq!(0, total); // error[E0502]: cannot borrow `total` as immutable because it is also borrowed as mutable (1..=10).for_each(add_mut_closure); // 今度は参照可能です assert_eq!(55, total);
値での取得(Move)
最後に値を取得する例です。
let last_word = "last word: ".to_owned(); let drop_closure = |sigh| { let res = String::new() + &last_word + sigh; drop(last_word); // これで値として取得 res }; // ここでは `last_word` にアクセスできません // assert_eq!("last word: ".to_owned(), last_word); // error[E0382]: borrow of moved value: `last_word` assert_eq!("last word: sigh!", drop_closure("sigh!")); // 再度呼び出すことはできません // assert_eq!("last word: sigh!", drop_closure("sigh!")); // error[E0382]: use of moved value: `drop_closure`
FnOnce トレイト
前述の例で、
drop_closure を二度呼び出そうとした際に現れたエラーを見てください。
note: closure cannot be invoked more than once because it moves the variable `last_word` out of its environment
これは閉包が実装する
FnOnce トレイトのためです。FnOnce は「少なくとも一度は呼び出せる」ことを示すトレイトで、コンパイラによって自動的に実装されます(安定版 Rust では手書きできません)。
FnOnce
閉包のデシグナリング
FnOncenightly と以下の機能を有効化すると、
drop_closure を構造体とトレイト実装に変換できます。
#![feature(fn_traits)] #![feature(unboxed_closures)]
struct DropStruct { last_word: String, } impl FnOnce<(&str,)> for DropStruct { type Output = String; extern "rust-call" fn call_once(self, (sigh,): (&str,)) -> Self::Output { let res = String::new() + &self.last_word + sigh; drop(self.last_word); res } }
使い方は次の通りです。
let last_word = "last word: ".to_owned(); let drop_struct = DropStruct { last_word }; assert_eq!("last word: sigh!", drop_struct("sigh!")); // 二度呼び出すとエラーになります。 // drop_struct("sigh!");
FnMut トレイト
変数を変更する閉包は
FnMut を実装します。以下の例を構造体に書き換えてみましょう。
let mut v = vec![]; let push_closure = |x| v.push(x); (1..=5).for_each(push_closure); assert_eq!(vec![1, 2, 3, 4, 5], v);
デシグナリングすると次のようになります。
struct PusherStruct<'a> { v: &'a mut Vec<i32>, } impl<'a> FnMut<(i32,)> for PusherStruct<'a> { extern "rust-call" fn call_mut(&mut self, (x,): (i32,)) -> Self::Output { self.v.push(x) } }
FnOnce も実装しなければならないので、追加で書きます。
impl<'a> FnOnce<(i32,)> for PusherStruct<'a> { type Output = (); extern "rust-call" fn call_once(mut self, args: (i32,)) -> Self::Output { self.call_mut(args) } }
Fn トレイト
複数回呼び出せ、可変参照を必要としない閉包は
Fn を実装します。先ほどの greeter_closure をデシグナリングしてみます。
let hello = "Hello "; let greeter_closure = |x| String::new() + hello + x; assert_eq!("Hello world", greeter_closure("world")); assert_eq!("Hello rust", greeter_closure("rust")); // 何度でも呼び出せる
構造体とトレイト実装に変換すると次のようになります。
struct GreeterStruct<'a> { hello: &'a str, } impl<'a, 'b> FnOnce<(&'b str,)> for GreeterStruct<'a> { type Output = String; extern "rust-call" fn call_once(self, args: (&'b str,)) -> Self::Output { self.call(args) } } impl<'a, 'b> FnMut<(&'b str,)> for GreeterStruct<'a> { extern "rust-call" fn call_mut(&mut self, args: (&'b str,)) -> Self::Output { self.call(args) } } impl<'a, 'b> Fn<(&'b str,)> for GreeterStruct<'b> { extern "rust-call" fn call(&self, (x,): (&'b str,)) -> Self::Output { String::new() + &self.hello + &x } }
使い方は次の通りです。
let hello = "Hello"; let greeter_struct = GreeterStruct { hello }; assert_eq!("Hello world", greeter_struct("world")); assert_eq!("Hello rust", greeter_struct("rust")); // 何度でも呼び出せる
move
キーワード
movemove を付けると、閉包はキャプチャした変数を所有権を移動させて取得します。参照が必要な場合でもこれにより所有権が移ります。
let hello = "Hello ".to_owned(); let greeter_closure = move |x| String::new() + &hello + x; // ここでは `hello` にアクセスできません // assert_eq!("Hello ", hello); // error[E0382]: borrow of moved value: `hello` assert_eq!("Hello world", greeter_closure("world"));
異なるキャプチャモードの理解
以下のダミー関数を定義します。
fn by_ref(_data: &String) {} fn by_mut(_data: &mut String) {} fn by_value(_data: String) {}
次に
move の有無とキャプチャモード(参照・可変参照・値)を組み合わせたテーブルです。
| Closure | FnOnce | FnMut | Fn |
|---|---|---|---|
| ✅ | ✅ | ✅ |
| ✅ | ✅ | ❌ |
| ✅ | ❌ | ❌ |
| ✅ | ✅ | ✅ |
| ✅ | ✅ | ❌ |
| ✅ | ❌ | ❌ |
move は実装されるトレイトを変えるわけではなく、取得方法(参照 vs. 値)だけが変わります。
デシグナリング例
move
を使わない場合
movestruct ByRefStruct<'a> { data: &'a String, } impl<'a> FnOnce<()> for ByRefStruct<'a> { type Output = (); extern "rust-call" fn call_once(self, args: ()) -> Self::Output { self.call(args) } } impl<'a> FnMut<()> for ByRefStruct<'a> { extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output { self.call(args) } } impl<'a> Fn<()> for ByRefStruct<'a> { extern "rust-call" fn call(&self, (): ()) -> Self::Output { by_ref(self.data) } }
move
を使う場合
movestruct MoveByRefStruct { data: String, } impl FnOnce<()> for MoveByRefStruct { type Output = (); extern "rust-call" fn call_once(self, args: ()) -> Self::Output { self.call(args) } } impl FnMut<()> for MoveByRefStruct { extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output { self.call(args) } } impl Fn<()> for MoveByRefStruct { extern "rust-call" fn call(&self, (): ()) -> Self::Output { by_ref(&self.data) } }
data の型が &'a String から String に変わり、by_ref 内で渡す引数も &self.data になっています。
実用例
スレッドを生成する場合
let data = "by_ref".to_owned(); std::thread::spawn(|| by_ref(&data)).join().unwrap(); // エラー: borrow may outlive current function
move を付ければ解決します。
std::thread::spawn(move || by_ref(&data)).join().unwrap();
関数から閉包を返す場合
fn make_greeter(greeter: &str) -> impl Fn(&str) -> String { move |name| format!("{greeter} {name}") } let hello_greeter = make_greeter("Hello"); assert_eq!(hello_greeter("rust"), "Hello rust");
move がないと、greeter のライフタイムが足りずコンパイラはエラーを出します。
まとめ
この記事はこれで終わります。後日 async 閉包についての追記事項を公開予定です。
さらに学びたい方には以下をおすすめします:
- Rust 本の closures チャプター
- Rust リファレンスの closures チャプター
- Baby Steps ブログの「explicit capture clause」記事
とfn_traits
を扱う Rust Unstable Bookunboxed_closures
ご清聴ありがとうございました。