ProgrammingRust開発者

Rustにおけるクロージャ(closures)はどのように実装されていますか?クロージャの種類にはどのようなものがあり、それぞれの違いや使用タイミングについて教えてください。

Hintsage AIアシスタントで面接を突破

回答

Rustにおいてクロージャは、外部スコープの変数を「キャプチャ」できる匿名関数です。構文は次の通りです:

let add = |a: i32, b: i32| a + b;

Rustには3種類のクロージャがあります(変数のキャプチャ方法によって異なります):

  • Fn: 参照(&T)をキャプチャし、環境をミュータブルにせずに何度でも呼び出すことができます。
  • FnMut: 変更可能な参照(&mut T)をキャプチャし、呼び出し時にクロージャ内のデータを変更できます。
  • FnOnce: 一度だけ呼び出すことができ、キャプチャされたデータの所有権を奪います(T)。

Rustは必要なタイプを自動的に判断しますが、明示的に指定することもできます。

違いの例:

let s = String::from("hello"); // FnOnce let consume = move || println!("{}", s); // sは呼び出し後にアクセスできなくなります

欺瞞的な質問

なぜmoveキーワードを持つクロージャはFnOnceではなくFnまたはFnMutになることがあるのですか?

回答: moveキーワードは所有権による変数のキャプチャを移動しますが、クロージャ内部のデータがミュータブルでなく、破壊されない場合、クロージャはFn/FnMutと互換性を保ちます。例えば:

let s = String::from("abc"); let c = move || println!("String: {}", s); // c: impl Fn() c();

cは何度でも呼び出すことができます:値sはクロージャにコピーされ、破壊されていません。

このトピックの微妙な点に関する実際のエラーの例


物語

データストリームプロジェクトの初心者Rust開発者が、moveなしでクロージャを書こうとし、別のスレッドに渡した結果、コンパイラーがoutlived borrowに対して文句を言い、ライフタイムやmoveの微妙な点を急いで理解する羽目になりました。


物語

クロージャ内でミュータビリティを導入したが、タイプをFnMutに変更せず、その結果iteratorを使用するすべての場所で「mismatched types」が発生しました。


物語

大きな配列を処理する際、クロージャが全コレクションをmoveで奪い、無意識に所有権を移動させてしまい、外部コードが最初のイテレーション後にデータにアクセスできなくなり、moved valueにアクセスしようとすることになりました。