Rustのクロージャ
クロージャ(closure)は、変数に入れたり、ほかの場所へ渡したりできる、名前のない小さな関数です。ふつうの関数との違いは、自分が作られたスコープにある変数を「キャプチャ」(取り込んで使うこと)できる点にあります。
クロージャの基本の書き方
Section titled “クロージャの基本の書き方”fn main() { let add_one = |x: i32| x + 1; println!("{}", add_one(5));}実行結果:
6縦棒で囲んだ |x: i32| が、ふつうの関数でいう丸かっこの代わりです。2つを並べて比べてみましょう。
fn add_one_fn(x: i32) -> i32 { x + 1 } // ふつうの関数let add_one_closure = |x: i32| x + 1; // 同じものをクロージャで書いたものRustは引数や戻り値の型を推論できることも多いので、クロージャはさらに短く書かれているのをよく見かけます。
let add_one = |x| x + 1;引数のないクロージャは、空の縦棒を使って let say_hi = || println!("やあ"); のように書き、say_hi() で呼び出します。
複数行のクロージャ
Section titled “複数行のクロージャ”式が1つで収まらないときは、関数と同じように本体を波かっこで囲みます。
fn main() { let describe = |n: i32| { let kind = if n % 2 == 0 { "偶数" } else { "奇数" }; format!("{n}は{kind}です") };
println!("{}", describe(7));}実行結果:
7は奇数ですformat! は println! と同じように使えますが、文字列を表示する代わりに String として返します。ここでは、その String が波かっこの中の最後の式なので、それがクロージャの戻り値になります。
周りのスコープから変数をキャプチャする
Section titled “周りのスコープから変数をキャプチャする”ここが、ふつうの関数にはない機能です。クロージャは、定義された場所にある変数を、引数として渡さなくてもそのまま使えます。
fn main() { let discount = 10; let apply_discount = |price: i32| price - discount;
println!("{}", apply_discount(100)); // 90 println!("{}", apply_discount(50)); // 40}実行結果:
9040discount はクロージャの引数ではありません。main のスコープにあるだけの変数ですが、クロージャはそこへ手を伸ばして直接使っています。ふつうの fn ではこれはできず、discount を引数として渡す必要があります。
キャプチャした変数を変更するクロージャ
Section titled “キャプチャした変数を変更するクロージャ”クロージャは、キャプチャした変数を変更することもできます。ただし、その変数が mut であることが条件です。さらにルールがもう1つあります。クロージャ自体も mut の変数に入れなければなりません。呼び出すと何かが変わるので、それがコードを見てわかるようにしたい、というのがRustの考え方です。
fn main() { let mut count = 0;
let mut increment = || { count += 1; println!("カウントは{count}になりました"); };
increment(); increment(); increment();
println!("最終カウント: {count}");}実行結果:
カウントは1になりましたカウントは2になりましたカウントは3になりました最終カウント: 3increment の mut を忘れると、コンパイラは何が問題なのかを正確に教えてくれます。calling increment requires mutable binding due to mutable borrow of count(「count を可変で借用しているので、increment を呼び出すには可変の束縛が必要」)というメッセージです。
知っておきたい落とし穴もあります。クロージャをまだ呼び出せる間は、クロージャが count を変更する権利を持っているので、ほかの誰も count に触れられません。2つの increment() の呼び出しの間に println!("{count}"); を入れると、error[E0502]: cannot borrow count as immutable because it is also borrowed as mutable(「count はすでに可変で借用されているので、不変で借用できない」)というエラーになります。最後の increment() の呼び出しが終われば、クロージャの出番は終わり、count はまた自由に使えるようになります。だから 最終カウント の行は動くのです。このルールの詳しい背景は、借用のページで学びます。
クロージャが、キャプチャしたものの所有権をまるごと受け取る必要があることもあります。たとえば、別のスレッドに渡すときです。そのときは、縦棒の前に move を書きます:move || { ... }。実際の使い方は並行処理のページで見ていきます。
クロージャが実際に使われる場面
Section titled “クロージャが実際に使われる場面”クロージャを単独で使うことは、あまりありません。本当に役に立つのは、ほかの関数に独自の処理を渡すときです。特に、イテレータのページで出てくるイテレータのメソッドと一緒によく使います。
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}実行結果:
[2, 4, 6, 8, 10]ここでは、|n| n * 2 というクロージャを .map() に直接渡しています。小さなクロージャで .map() や .filter() のようなメソッドの動きを決める、というこの形こそ、ふだんクロージャを使う場面です。
1. 税込み価格
Section titled “1. 税込み価格”tax_percent という変数を20で作ります。次に、価格を受け取り、キャプチャした tax_percent を使って税込み価格を返すクロージャ with_tax を書いてください。価格50と120の結果を表示しましょう。ヒント:税額は price * tax_percent / 100 です。
解答を見る
fn main() { let tax_percent = 20; let with_tax = |price: i32| price + price * tax_percent / 100;
println!("50の税込み価格は{}", with_tax(50)); println!("120の税込み価格は{}", with_tax(120));}実行結果:
50の税込み価格は60120の税込み価格は144tax_percent は一度も引数として渡していません。クロージャが main からキャプチャしているのです。これこそ、ふつうの fn にはできないことです。
2. IDを発行する
Section titled “2. IDを発行する”1回目に呼び出すと1、2回目は2、3回目は3……と返すクロージャ next_id を書いてください。3回呼び出して、それぞれのIDを表示しましょう。ヒント:クロージャの外に mut のカウンタを置き、中で1を足します。カウンタをクロージャの最後の式にすれば、それが返されます。
解答を見る
fn main() { let mut last_id = 0;
let mut next_id = || { last_id += 1; last_id };
println!("1人目のユーザーのIDは{}", next_id()); println!("2人目のユーザーのIDは{}", next_id()); println!("3人目のユーザーのIDは{}", next_id());}実行結果:
1人目のユーザーのIDは12人目のユーザーのIDは23人目のユーザーのIDは3last_id と next_id の両方に mut が必要です。last_id は値が変わるから、next_id は呼び出すとキャプチャしたものが変わるからです。
- クロージャは名前のない小さな関数で、
|引数| 式の形で書きます。 - 型が推論されることが多いので、同じ働きの
fnよりたいてい短く書けます。 - ふつうの関数と違って、クロージャは定義されたスコープの変数をキャプチャできます。
- キャプチャした変数を変更するクロージャは
mutの変数に入れる必要があり、使われている間はその変数を借用し続けます。 - クロージャがいちばん役立つのは、ほかの関数、特に
.map()のようなイテレータのメソッドに渡すときです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. クロージャでは、関数の丸かっこの代わりに、どんな記号で引数を囲みますか?
2. ふつうの fn にはできなくて、クロージャにできることは何ですか?
3. このページによると、クロージャが実際にいちばんよく使われるのはどんな場面ですか?
4. クロージャが、キャプチャした変数に count += 1 をしています。ほかに何が必要ですか?
スコア: 0 / 4
さらに詳しく
Section titled “さらに詳しく”- クロージャ:環境をキャプチャする匿名関数 — The Rust Programming Language 日本語版:クロージャ、型推論、
Fn系トレイトを解説した公式の章です。 - 参照をキャプチャするか所有権を移動するか — The Rust Programming Language 日本語版:クロージャが変数をキャプチャする3つの方法と、
moveを使うべき場面を説明しています。 - クロージャ — Rust By Example 日本語版:クロージャと関数を比べる、短い実行できるサンプルです。
- 要素の捕捉 — Rust By Example 日本語版:変数を読む・変更する・所有権を受け取るクロージャの例がさらに載っています。
- Closure expressions — The Rust Reference(英語):クロージャの書き方とキャプチャについての正確なルールです。