クロージャー

概要

  • Rustのラムダ式クロージャーと呼ぶ
    • 関数ポインタ(fn)は、外部環境変数を含めることができない
    • 関数ポインタもクロージャートレイトを実装している
  • クロージャーの種類 (キャプチャー方法が異なる)
    • FnOnce<Args>: selfメソッド, キャプチャー変数を消費する。
    • FnMut<Args> : &mut selfメソッド, 環境を変更するため、値を借用する。
    • Fn<Args> : &selfメソッド, 環境から不変的に値を借用する。
  • 所有権をクロージャー(Fn・FnMut)に移動するには、moveキーワードを使用する
Fn FnMut FnOnce
キャプチャー方法 コピー ミュータブル参照 移動

詳細

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

  • 外部変数のキャプチャーは、ミュータブル参照する
  • FnMutクロージャーには、mutキーワードを付ける必要がある
  • 何度もクロージャーを実行できる

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 キーワードによってキャプチャー変数の所有権をクロージャ内部に移すことができる