クロージャ

しばしば、関数と 自由変数 を一つにまとめておくことがコードの明確さや再利用に役立つ場合が有ります。 自由変数は外部のスコープから来て、関数中で使われるときに「閉じ込め」られます。 そのためそのようなまとまりを「クロージャ」と呼び、 Rustはこれから見ていくようにクロージャの非常に良い実装を提供しています。

構文

クロージャは以下のような見た目です:

fn main() { let plus_one = |x: i32| x + 1; assert_eq!(2, plus_one(1)); }
let plus_one = |x: i32| x + 1;

assert_eq!(2, plus_one(1));

束縛 plus_one を作成し、クロージャを代入しています。 クロージャの引数はパイプ( | )の間に書きます、そしてクロージャの本体は式です、 この場合は x + 1 がそれに当たります。 { } が式であることを思い出して下さい、 そのため複数行のクロージャを作成することも可能です:

fn main() { let plus_two = |x| { let mut result: i32 = x; result += 1; result += 1; result }; assert_eq!(4, plus_two(2)); }
let plus_two = |x| {
    let mut result: i32 = x;

    result += 1;
    result += 1;

    result
};

assert_eq!(4, plus_two(2));

いくつかクロージャと通常の fn で定義される関数との間の違いに気がつくことでしょう。 一つ目はクロージャの引数や返り値の型を示す必要が無いことです。 型を以下のように示すことも可能です:

fn main() { let plus_one = |x: i32| -> i32 { x + 1 }; assert_eq!(2, plus_one(1)); }
let plus_one = |x: i32| -> i32 { x + 1 };

assert_eq!(2, plus_one(1));

しかし、このように型を示す必要はありません。 なぜでしょう?一言で言えば、これは使いやすさのためです。 名前の有る関数の型を全て指定するのはドキュメンテーションや型推論の役に立ちますが、 クロージャの型は殆ど示されません、これはクロージャたちが匿名であり、 さらに名前付きの関数が引き起こすと思われるような定義から離れた箇所で発生するエラーの要因ともならないためです。

通常の関数との違いの二つ目は、構文が大部分は似ていますがほんの少しだけ違うという点です。 比較がしやすいようにスペースを適宜補って以下に示します:

fn main() { fn plus_one_v1 (x: i32) -> i32 { x + 1 } let plus_one_v2 = |x: i32| -> i32 { x + 1 }; let plus_one_v3 = |x: i32| x + 1 ; }
fn  plus_one_v1   (x: i32) -> i32 { x + 1 }
let plus_one_v2 = |x: i32| -> i32 { x + 1 };
let plus_one_v3 = |x: i32|          x + 1  ;

小さな違いは有りますが殆どの部分は同じです。

クロージャとクロージャの環境

クロージャの環境は引数やローカルな束縛に加えてクロージャを囲んでいるスコープ中の束縛を含むことができます。 例えば以下のようになります:

fn main() { let num = 5; let plus_num = |x: i32| x + num; assert_eq!(10, plus_num(5)); }
let num = 5;
let plus_num = |x: i32| x + num;

assert_eq!(10, plus_num(5));

クロージャ plus_num はスコープ内の let 束縛 num を参照しています。 より厳密に言うと、クロージャ plus_num は束縛を借用しています。 もし、この束縛と衝突する処理を行うとエラーが発生します。 例えば、以下のようなコードでは:

fn main() { let mut num = 5; let plus_num = |x: i32| x + num; let y = &mut num; }
let mut num = 5;
let plus_num = |x: i32| x + num;

let y = &mut num;

以下のエラーを発生させます:

error: cannot borrow `num` as mutable because it is also borrowed as immutable
    let y = &mut num;
                 ^~~
note: previous borrow of `num` occurs here due to use in closure; the immutable
  borrow prevents subsequent moves or mutable borrows of `num` until the borrow
  ends
    let plus_num = |x| x + num;
                   ^~~~~~~~~~~
note: previous borrow ends here
fn main() {
    let mut num = 5;
    let plus_num = |x| x + num;

    let y = &mut num;
}
^

冗長ですが役に立つエラーメッセージです! エラーが示しているように、クロージャが既に num を借用しているために、 num の変更可能な借用を取得することはできません。 もしクロージャがスコープ外になるようにした場合以下のようにできます:

fn main() { let mut num = 5; { let plus_num = |x: i32| x + num; // } // plus_num goes out of scope, borrow of num ends } // plus_numがスコープ外に出て、numの借用が終わります let y = &mut num; }
let mut num = 5;
{
    let plus_num = |x: i32| x + num;

} // plus_numがスコープ外に出て、numの借用が終わります

let y = &mut num;

もしクロージャが num を要求した場合、Rustは借用する代わりに環境の所有権を取りムーブします。 そのため、以下のコードは動作しません:

fn main() { let nums = vec![1, 2, 3]; let takes_nums = || nums; println!("{:?}", nums); }
let nums = vec![1, 2, 3];

let takes_nums = || nums;

println!("{:?}", nums);

このコードは以下の様なエラーを発生させます:

note: `nums` moved into closure environment here because it has type
  `[closure(()) -> collections::vec::Vec<i32>]`, which is non-copyable
let takes_nums = || nums;
                 ^~~~~~~

Vec<T> はその要素に対する所有権を持っています、 それゆえそれらの要素をクロージャ内で参照した場合、 num の所有権を取ることになります。 これは numnum の所有権を取る関数に渡した場合と同じです。

move クロージャ

move キーワードを用いることで、クロージャに環境の所有権を取得することを強制することができます。

fn main() { let num = 5; let owns_num = move |x: i32| x + num; }
let num = 5;

let owns_num = move |x: i32| x + num;

このようにすると move というキーワードにもかかわらず、変数は通常のmoveのセマンティクスに従います。 この場合、 5Copy を実装しています、 そのため owns_numnum のコピーの所有権を取得します。 では、なにが異なるのでしょうか?

fn main() { let mut num = 5; { let mut add_num = |x: i32| num += x; add_num(5); } assert_eq!(10, num); }
let mut num = 5;

{
    let mut add_num = |x: i32| num += x;

    add_num(5);
}

assert_eq!(10, num);

このケースでは、クロージャは num の変更可能な参照を取得し、 add_num を呼び出した時、期待通りに num の値を変更します。 またクロージャ add_num はその環境を変更するため mut として宣言する必要があります。

もしクロージャを move に変更した場合、結果が異なります:

fn main() { let mut num = 5; { let mut add_num = move |x: i32| num += x; add_num(5); } assert_eq!(5, num); }
let mut num = 5;

{
    let mut add_num = move |x: i32| num += x;

    add_num(5);
}

assert_eq!(5, num);

結果は 5 になります。 num の変更可能な借用を取得するのではなく、 num のコピーの所有権を取得します。

move クロージャを捉えるもう一つの観点は: move クロージャは独自のスタックフレームを持っているという点です。 move クロージャは自己従属していますが、 move でないクロージャはクロージャを作成したスタックフレームと紐付いています。 これは一般的に、move でないクロージャを関数から返すことはできないということを意味しています。

クロージャを引数や返り値にすることについて説明する間に、クロージャの実装についてもう少し説明する必要があります。 システム言語としてRustはコードの動作についてコントロールする方法を大量に提供しています、 そしてそれはクロージャも例外ではありません。

クロージャの実装

Rustにおけるクロージャの実装は他の言語とは少し異なります。 Rustにおけるクロージャは実質的にトレイトへの糖衣構文です。 続きの説明を読む前に トレイトトレイトオブジェクト についてのチャプターを学ぶ前に読みたくなるでしょう。

よろしいですか? では、続きを説明いたします。

クロージャの内部的な動作を理解するための鍵は少し変わっています: 関数を呼び出すのに () を 例えば foo() の様に使いますが、この () はオーバーロード可能な演算子です。 この事実から残りの全てを正しく理解することができます。 Rustでは、トレイトを演算子のオーバーロードに利用します。 それは関数の呼び出しも例外ではありません。 () をオーバーロードするのに利用可能な、3つの異なるトレイトが存在します:

fn main() { mod foo { pub trait Fn<Args> : FnMut<Args> { extern "rust-call" fn call(&self, args: Args) -> Self::Output; } pub trait FnMut<Args> : FnOnce<Args> { extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; } pub trait FnOnce<Args> { type Output; extern "rust-call" fn call_once(self, args: Args) -> Self::Output; } } }
pub trait Fn<Args> : FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

pub trait FnMut<Args> : FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

pub trait FnOnce<Args> {
    type Output;

    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

これらのトレイトの間のいくつかの違いに気がつくことでしょう、しかし大きな違いは self についてです: Fn&self を引数に取ります、 FnMut&mut self を引数に取ります、そして FnOnceself を引数に取ります。 これは通常のメソッド呼び出しにおける self のすべての種類をカバーしています。 しかし、これら self の各種類を一つの大きなトレイトにまとめるのではなく異なるトレイトに分けています。 このようにすることで、どのような種類のクロージャを取るのかについて多くをコントロールすることができます。

クロージャの構文 || {} は上述の3つのトレイトへの糖衣構文です。 Rustは環境用の構造体を作成し、 適切なトレイトを impl し、それを利用します。

クロージャを引数に取る

クロージャが実際にはトレイトであることを学んだので、 クロージャを引数としたり返り値としたりする方法を既に知っていることになります: 通常のトレイトと同様に行うのです!

これは、静的ディスパッチと動的ディスパッチを選択することができるということも意味しています。 手始めに呼び出し可能な何かを引数にとり、それを呼び出し、結果を返す関数を書いてみましょう:

fn main() { fn call_with_one<F>(some_closure: F) -> i32 where F : Fn(i32) -> i32 { some_closure(1) } let answer = call_with_one(|x| x + 2); assert_eq!(3, answer); }
fn call_with_one<F>(some_closure: F) -> i32
    where F : Fn(i32) -> i32 {

    some_closure(1)
}

let answer = call_with_one(|x| x + 2);

assert_eq!(3, answer);

クロージャ |x| x + 2call_with_one に渡しました。 call_with_one はその関数名から推測される処理を行います: クロージャに 1 を与えて呼び出します。

call_with_one のシグネチャを詳細に見ていきましょう:

fn main() { fn call_with_one<F>(some_closure: F) -> i32 where F : Fn(i32) -> i32 { some_closure(1) } }
fn call_with_one<F>(some_closure: F) -> i32

F の引数を1つ取り、返り値として i32 を返します。 この部分は特に注目には値しません。次の部分は:

fn main() { fn call_with_one<F>(some_closure: F) -> i32 where F : Fn(i32) -> i32 { some_closure(1) } }
    where F : Fn(i32) -> i32 {

Fn がトレイトであるために、ジェネリックの境界として Fn を指定することができます。 この場合はクロージャは i32 を引数として取り、 i32 を返します、そのため ジェネリックの境界として Fn(i32) -> i32 を指定します。

キーポイントがほかにもあります: ジェネリックをトレイトで境界を指定したために、 この関数は単相化され、静的ディスパッチをクロージャに対して行います。これはとても素敵です。 多くの言語では、クロージャは常にヒープにアロケートされ、常に動的ディスパッチが行われます。 Rustではスタックにクロージャの環境をアロケートし、呼び出しを静的ディスパッチすることができます。 これは、しばしばクロージャを引数として取る、イテレータやそれらのアダプタにおいて頻繁に行われます。

もちろん、動的ディスパッチを行いたいときは、そうすることもできます。 そのような場合もトレイトオブジェクトが通常どおりに対応します:

fn main() { fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 { some_closure(1) } let answer = call_with_one(&|x| x + 2); assert_eq!(3, answer); }
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}

let answer = call_with_one(&|x| x + 2);

assert_eq!(3, answer);

トレイトオブジェクト &Fn を引数にとります。 また call_with_one にクロージャを渡すときに参照を利用するようにしました、 そのため &|| を利用しています。

関数ポインタとクロージャ

関数ポインタは環境を持たないクロージャのようなものです。 そのため、クロージャを引数として期待している関数に関数ポインタを渡すことができます。

fn main() { fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 { some_closure(1) } fn add_one(i: i32) -> i32 { i + 1 } let f = add_one; let answer = call_with_one(&f); assert_eq!(2, answer); }
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}

fn add_one(i: i32) -> i32 {
    i + 1
}

let f = add_one;

let answer = call_with_one(&f);

assert_eq!(2, answer);

この例では、中間の変数 f が必ずしも必要なわけではありません、関数名を指定することでもきちんと動作します:

fn main() { let answer = call_with_one(&add_one); }
let answer = call_with_one(&add_one);

クロージャを返す

関数を用いたスタイルのコードでは、クロージャを返すことは非常によく見られます。 もし、クロージャを返すことを試みた場合、エラーが発生します。これは一見奇妙に思われますが、理解することができます。 以下は、関数からクロージャを返すことを試みた場合のコードです:

fn main() { fn factory() -> (Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer); }
fn factory() -> (Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);

このコードは以下の長いエラーを発生させます:

error: the trait `core::marker::Sized` is not implemented for the type
`core::ops::Fn(i32) -> i32` [E0277]
fn factory() -> (Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
fn factory() -> (Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~
error: the trait `core::marker::Sized` is not implemented for the type `core::ops::Fn(i32) -> i32` [E0277]
let f = factory();
    ^
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
let f = factory();
    ^

関数から何かを返すにあたって、Rustは返り値の型のサイズを知る必要があります。 しかし、 Fn はトレイトであるため、そのサイズや種類は多岐にわたることになります: 多くの異なる型が Fn を実装できます。 何かにサイズを与える簡単な方法は、それに対する参照を取得する方法です、参照は既知のサイズを持っています。 そのため、以下のように書くことができます:

fn main() { fn factory() -> &(Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer); }
fn factory() -> &(Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);

しかし、他のエラーが発生してしまいます:

error: missing lifetime specifier [E0106]
fn factory() -> &(Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~~

ふむ。これはリファレンスを利用したので、ライフタイムを指定する必要が有るためです。 しかし、 factory() 関数は引数を何も取りません、 そのため ライフタイムの省略 は実施されません。 では、どのような選択肢が有るのでしょうか? 'static を試してみましょう:

fn main() { fn factory() -> &'static (Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer); }
fn factory() -> &'static (Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);

しかし、以下の別のエラーが発生します:

error: mismatched types:
 expected `&'static core::ops::Fn(i32) -> i32`,
    found `[closure@<anon>:7:9: 7:20]`
(expected &-ptr,
    found closure) [E0308]
         |x| x + num
         ^~~~~~~~~~~

このエラーは &'static Fn(i32) -> i32 ではなく、 [closure@<anon>:7:9: 7:20] を使ってしまっているということを伝えています。 ちょっと待ってください、一体これはどういう意味でしょう?

それぞれのクロージャはそれぞれの環境用の struct を生成し、 Fn やそれに準ずるものを実装するため、それぞれの型は匿名となります。 それらの型はそれらのクロージャのためだけに存在します。 そのためRustはそれらの型を自動生成された名前の代わりに closure@<anon> と表示します。

また、このエラーは返り値の型が参照であることを期待しているが、 上のコードではそうなっていないということについても指摘しています。 もうちょっというと、直接的に 'static ライフタイムをオブジェクトに割り当てることはできません。 そこで、Fn をボックス化することで「トレイトオブジェクト」を返すという方法を取ります。 そうすると、動作するまであと一歩のところまで来ます:

fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(|x| x + num) } fn main() { let f = factory(); let answer = f(1); assert_eq!(6, answer); }
fn factory() -> Box<Fn(i32) -> i32> {
    let num = 5;

    Box::new(|x| x + num)
}
let f = factory();

let answer = f(1);
assert_eq!(6, answer);

最後に残されたエラーは以下のとおりです:

error: closure may outlive the current function, but it borrows `num`,
which is owned by the current function [E0373]
Box::new(|x| x + num)
         ^~~~~~~~~~~

以前説明したように、クロージャはその環境を借用します。 今回の場合は、環境はスタックにアロケートされた 5 に束縛された num からできていることから、 環境の借用はスタックフレームと同じライフタイムを持っています。 そのため、もしこのクロージャを返り値とした場合、 そのあと factory() 関数の処理は終了し、スタックフレームが取り除かれクロージャはゴミとなったメモリを参照することになります! 上のコードに最後の修正を施すことによって動作させることができるようになります:

fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(move |x| x + num) } fn main() { let f = factory(); let answer = f(1); assert_eq!(6, answer); }
fn factory() -> Box<Fn(i32) -> i32> {
    let num = 5;

    Box::new(move |x| x + num)
}
let f = factory();

let answer = f(1);
assert_eq!(6, answer);

factory() 内のクロージャを move Fn にすることで、新しいスタックフレームをクロージャのために生成します。 そしてボックス化することによって、既知のサイズとなり、現在のスタックフレームから抜けることが可能になります。