Rust by Example

8.2.2 捕捉時の型推論

ここまでで、アノテーションのない変数を、Rustが実行時に捕捉する例を何度か見てきました。通常はこれは大変便利なのですが、関数を書く際には、このように曖昧にしておくことは許されません。クロージャが捕捉する型を含むクロージャの完全型をはっきりさせておく必要があります。クロージャが外部変数を捕捉する方法は以下のトレイトで明示されています。

  • Fn: は変数をリファレンス(&T)で捕捉する。
  • FnMut: は変数をミュータブルなリファレンス(&mut T)で捕捉する。
  • FnOnce: は変数を値(T)で捕捉する。

このように明示されていても、クロージャの柔軟性は損なわれません。というのもFnOnceは、クロージャがTだけではなく、&mut T&Tのいずれかをとることが可能であるということを示しているにすぎないからです。(値の移動(move)が可能ならば、いかなるタイプの借用も可能です。)逆にFn&mut TTをとることはできません。ゆえに捕捉のルールを一言でいうと

  • それぞれのトレイトは自分、あるいは上位のトレイトの型に補足対象を限定する。

となります。

訳注: わかりにくいですが、要するにFn =< FnMut =< FnOnceという包含関係が成り立つということを言っています。

付け加えると、Rustは変数を出来る限り制限の少ない方法(訳注: つまり&T)で補足します。

// クロージャを引数に取る関数 fn apply<F>(f: F) where // クロージャには引数も返り値もない。 F: FnOnce() { // ^ TODO: ここを`Fn`あるいは`FnMut`に変えてみましょう。 f() } // クロージャを引数に取り、`i32`を返す関数 fn apply_to_3<F>(f: F) -> i32 where // このクロージャは引数、返り値ともに`i32` F: Fn(i32) -> i32 { f(3) } fn main() { use std::mem; let greeting = "hello"; // コピーではなくmoveが起きる型 let mut farewell = "goodbye".to_owned(); // 変数を2つ補足。`greeting`は参照を、 // `farewell`は値をそれぞれ捕捉する。 let diary = || { // `greeting`は参照なので、`Fn`が必要。 println!("I said {}.", greeting); // `farewell`の値を変更するので、この時点で`FnMut` // が必要になる。 farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); println!("Now I can sleep. zzzzz"); // `mem::drop`を明示的に呼ぶと`farewell`が値で // 捕捉される必要性が発生する。よって`FnOnce`が必要になる。 mem::drop(farewell); }; // クロージャを適用する関数を実行。 apply(diary); let double = |x| 2 * x; println!("3 doubled: {}", apply_to_3(double)); }

See also:

std::mem::drop, Fn, FnMut, and FnOnce