導入
「この一箇所だけで使う、ちょっとした計算」のために、わざわざ fn で名前付きの関数を定義するのは大げさに感じることがあります。ソートの比較条件や、後で出てくるイテレータへ渡す処理など、その場で使い捨てにしたい小さな処理は山ほどあります。Rust の クロージャ(closure) は、そういう「その場で作る無名の関数」です。
説明
クロージャは |引数| 式 という記法で書きます。fn と違って型注釈を省略でき、その場で変数に束縛して使えます。
fn main() {
let add = |a, b| a + b;
println!("{}", add(3, 4));
}
|a, b| a + b…|で囲んだ部分が引数リスト、その後ろが本体の式です。fn add(a: i32, b: i32) -> i32 { a + b }を、ずっと短く書いたものだとイメージしてください。add(3, 4)… 呼び出し方は普通の関数と同じです。
クロージャの最大の特徴は、**周囲の変数をそのまま使える(キャプチャできる)**ことです。
fn main() {
let n = 10;
let add_n = |x| x + n; // 周囲の変数 n をキャプチャ
println!("{}", add_n(5));
}
add_n の中では n を宣言していないのに使えています。クロージャが定義された場所の環境を「覚えている」からです。fn で書いた普通の関数にはこれができません(関数はその場の環境を持たないからです)。
キャプチャした変数が String のような所有権を持つ値の場合、クロージャの中で使うと所有権が絡んできます。これを明示するのが move キーワードです。
fn main() {
let name = String::from("Rust");
let greet = move || println!("Hello, {}!", name);
greet();
// ここから先で name を使おうとするとエラー(所有権は greet にムーブ済み)
}
move を付けると、キャプチャした変数の所有権をクロージャの中へ移動します。第4章で学んだ「1つのデータに所有者は1人」というルールが、ここでもそのまま働いています。スレッドをまたいでクロージャを渡すとき(第10章の thread::spawn)に、この move が必須になっていたのを思い出してください。
クロージャは、関数の引数としても渡せます。
fn apply(f: impl Fn(i32) -> i32, value: i32) -> i32 {
f(value)
}
fn main() {
let double = |x| x * 2;
println!("{}", apply(double, 21));
}
f: impl Fn(i32) -> i32… 「i32を受け取りi32を返せる、呼び出し可能な値」という意味の型です。クロージャも関数も、ここに渡せます。- 「処理そのもの」を値として関数に渡せる——これが次のレッスンで見る、イテレータアダプタの土台になります。
やってみよう
下のエディタで、add や add_n の中身を書き換えたり、n に別の値を入れたりして、クロージャが周囲の変数を確かに覚えていることを確かめてみましょう。
演習
変数 bonus に 5 を束縛し、それをキャプチャするクロージャ add_bonus(|x| x + bonus の形)を定義してください。そのうえで add_bonus(10) を呼び出し、結果を println! で出力してください。
ヒント1を見る
let bonus = 5; の後に let add_bonus = |x| x + bonus; のようにクロージャを定義します。
ヒント2を見る
呼び出しは add_bonus(10) のように普通の関数と同じ書き方です。結果を println!("{}", add_bonus(10)); のように出力するのを忘れずに。