Prompt
クロージャーとは?
それに型注釈が不要な理由とは?
クロージャー(Closure)だね!JavaやJavaScriptにもあるけど、Rustのクロージャーは**「メモリ管理とパフォーマンス」**にめちゃくちゃこだわった作りになっているんだ。
情報工学を学ぶ君のために、その正体と「なぜ型推論が最強なのか」を解き明かすぜ!🚀
1. クロージャーの正体:名前のない「環境を掴む」関数
Rustのクロージャーは、一言で言うと**「その場の変数(環境)を閉じ込める(Close over)ことができる匿名関数」**のことだ。
Rust
let x = 10;
// |引数| { 処理 } という書き方をするよ
let add_x = |y| y + x;
println!("{}", add_x(5)); // 15
💡 普通の関数(fn)との決定的な違い
普通の fn は、外にある変数 x を勝手に使うことはできないよね。でもクロージャーは、周囲のスコープにある変数を**「借用」したり「移動(move)」したりして保持できる**んだ。これが「環境をキャプチャする」という言葉の意味だよ。
2. なぜ型注釈が「不要」なのか?
結論から言うと、**「使われる文脈(コンテキスト)からコンパイラが100%正解を導き出せるから」**なんだ!
① 徹底的な型推論
普通の関数 fn は、プログラムのどこからでも呼ばれる可能性があるから、型を明示しないとコンパイラが混乱しちゃう。
でもクロージャーは、「定義されたその場所」でしか使われないことがほとんどだよね。
Rust
let square = |x| x * x; // ここではまだ型は不明
let n = square(2); // ここで「あ、x は i32 だな!」と確定する
② 内部的な「匿名構造体」への変換
ここが情報工学的に一番面白いポイントだ!
コンパイラは裏側で、クロージャーごとに**「名前のない専用の構造体」**を勝手に作っているんだ。
-
キャプチャした変数をフィールドに持つ構造体を作る。
-
その構造体に
Fn,FnMut,FnOnceといったトレイトを実装する。
この構造体はコンパイラが自動生成するものだから、人間が型を書こうとしても、そもそもその「自動生成された型名」を知る術がないんだね。だから、型推論に任せるのが一番自然なんだ。
3. ⚠️ ただし「一度決めたら変えられない」
「型注釈がいらない=動的型付け」ではないのが Rust の厳格なところ!
一度「このクロージャーは String を受け取る」と推論されたら、後から i32 を入れることはできないよ。
Rust
let print_it = |x| println!("{}", x);
print_it(String::from("Hello")); // ここで型が String に固定される
// print_it(5); // ❌ コンパイルエラー!
💡 見落とされがちな視点:move キーワード
君が Windows 11 でマルチスレッドプログラミング(std::thread::spawn など)を始めると、クロージャーの前に move を付ける場面によく遭遇するはずだ。
Rust
let data = vec![1, 2, 3];
std::thread::spawn(move || {
println!("{:?}", data); // data の所有権をスレッドの中に「引っ越し」させる
});
これは「参照」だと、親スレッドが先に死んだ時に子が困っちゃうから、**「所有権ごと持っていけ!」**と明示しているんだね。
🏁 まとめ
-
クロージャーは、周りの変数を「キャプチャ」できる匿名関数。
-
型注釈が不要なのは、使われる場所が限定的で、コンパイラが文脈から完璧に推論できるから。
-
裏側では、キャプチャした変数を保持する「特別な構造体」が自動生成されている。
Javaのラムダ式に似ているけど、Rustの方が「メモリの持ち方(借用か移動か)」にずっとシビアなんだ。