クロージャー
概要
- Rustのラムダ式はクロージャーと呼ぶ
- クロージャーの種類 (キャプチャー方法が異なる)
FnOnce<Args>
:self
メソッド, キャプチャー変数を消費する。FnMut<Args>
:&mut self
メソッド, 環境を変更するため、値を借用する。Fn<Args>
:&self
メソッド, 環境から不変的に値を借用する。
- 所有権をクロージャー(Fn・FnMut)に移動するには、
move
キーワードを使用する
クロージャー | キャプチャー方法 |
---|---|
FnOnce | 移動 |
FnMut | ミュータブル参照 |
Fn | コピー |
詳細
trait FnOnce
- 外部変数のキャプチャーは、所有権を移動する
- クロージャーを実行できるのは一回のみ
Exmaple
クロージャー内部で所有権を移動すると自動でFnOnce
トレイトになる
let m: String = String::from("hello"); let fn_once = || { let s = m; // 所有権を移動 ... })
クロージャーは一回だけ実行できる
fn take_once<F: FnOnce()>(f: F) { f(); // 内部で所有権が移動している f(); // NG }
trait FnMut: FnOnce
Example
クロージャー内部で外部変数を変更できる
let mut s = String::from(""); let mut fnmut = |str| { let m = &mut s; m.push_str(str); }; fnmut("hi"); fnmut(" "); fnmut("there"); let mut count = 0; let mut counter = || { count += 1; // mut変数をキャプチャーするとFnMutクロージャーになる }; counter(); counter(); }
trait Fn: FnMut
- 外部変数は全てコピーされる (キャプチャーする値はCopyトレイトを実装している必要がある)
Example
#[derive(Debug,Copy, Clone)] struct Foo(i32); impl Foo { fn increment(&mut self) { self.0 += 1; } } fn call_times<F: Fn()>(time: i32, f: F) { for _ in 0..time { f(); } } fn main() { let f = Foo(3); call_times(10, || { let mut s = f; // Copyが発生する s.increment(); }); dbg!(f.0); // 3 }
所有権の強制移動 move
キーワード
ローカル変数の値をキャプチャーしたクロージャーを返すとき、ローカル変数の値の所有権をクロージャーに移す必要がある。
fn counter(start: i32) -> impl FnMut() -> i32 { let mut s = start; return || { s += 1; s }; } /* error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function | 4 | return || { | ^^ may outlive borrowed value `s` 5 | s += 1; | - `s` is borrowed here error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function | 4 | return || { | ^^ may outlive borrowed value `s` 5 | s += 1; | - `s` is borrowed here | note: closure is returned here | 1 | fn counter(start: i32) -> impl FnMut() -> i32 | ^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword | 4 | return move || { | ^^^^^^^
move
キーワードを使って、s
の所有権をクロージャーに渡す必要がある。
fn counter(start: i32) -> impl FnMut() -> i32 { let mut s = start; return move || { s += 1; s }; }
まとめ
- Fn 外部変数のキャプチャーは、コピーが発生する
- FnMut 外部変数のキャプチャーは、ミュータブル参照が発生する
- FnOnce 外部変数のキャプチャーは、所有権が移動する
move
キーワードによってキャプチャー変数の所有権をクロージャ内部に移すことができる