注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
しばしば、関数と 自由変数 を一つにまとめておくことがコードの明確さや再利用に役立つ場合が有ります。 自由変数は外部のスコープから来て、関数中で使われるときに「閉じ込め」られます。 そのためそのようなまとまりを「クロージャ」と呼び、 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
がそれに当たります。
{ }
が式であることを思い出して下さい、 そのため複数行のクロージャを作成することも可能です:
let plus_two = |x| { let mut result: i32 = x; result += 1; result += 1; result }; assert_eq!(4, plus_two(2));
いくつかクロージャと通常の fn
で定義される関数との間の違いに気がつくことでしょう。
一つ目はクロージャの引数や返り値の型を示す必要が無いことです。
型を以下のように示すことも可能です:
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
は束縛を借用しています。
もし、この束縛と衝突する処理を行うとエラーが発生します。
例えば、以下のようなコードでは:
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
の変更可能な借用を取得することはできません。
もしクロージャがスコープ外になるようにした場合以下のようにできます:
let mut num = 5; { let plus_num = |x: i32| x + num; } // plus_numがスコープ外に出て、numの借用が終わります let y = &mut num;
もしクロージャが num
を要求した場合、Rustは借用する代わりに環境の所有権を取りムーブします。
そのため、以下のコードは動作しません:
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>
はその要素に対する所有権を持っています、
それゆえそれらの要素をクロージャ内で参照した場合、 nums
の所有権を取ることになります。
これは nums
を nums
の所有権を取る関数に渡した場合と同じです。
move
クロージャmove
キーワードを用いることで、クロージャに環境の所有権を取得することを強制することができます。
let num = 5; let owns_num = move |x: i32| x + num;
このようにすると move
というキーワードにもかかわらず、変数は通常のmoveのセマンティクスに従います。
この場合、 5
は Copy
を実装しています、
そのため owns_num
は 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
に変更した場合、結果が異なります:
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つの異なるトレイトが存在します:
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
を引数に取ります、そして FnOnce
は self
を引数に取ります。
これは通常のメソッド呼び出しにおける 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 + 2
を call_with_one
に渡しました。
call_with_one
はその関数名から推測される処理を行います: クロージャに 1
を与えて呼び出します。
call_with_one
のシグネチャを詳細に見ていきましょう:
fn call_with_one<F>(some_closure: F) -> i32
型 F
の引数を1つ取り、返り値として i32
を返します。
この部分は特に注目には値しません。次の部分は:
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
が必ずしも必要なわけではありません、関数名を指定することでもきちんと動作します:
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 bound `core::ops::Fn(i32) -> i32 : core::marker::Sized` is not satisfied [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 bound `core::ops::Fn(i32) -> i32 : core::marker::Sized` is not satisfied [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 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 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) } 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) } let f = factory(); let answer = f(1); assert_eq!(6, answer);
factory()
内のクロージャを move Fn
にすることで、新しいスタックフレームをクロージャのために生成します。
そしてボックス化することによって、既知のサイズとなり、現在のスタックフレームから抜けることが可能になります。