本文へスキップ
BecomeCoder

Rustコース · 第11章 Rustらしい書き方 ― クロージャとイテレータ · レッスン43

クロージャ ― |x| 記法と環境のキャプチャ

ブラウザで完結

導入

「この一箇所だけで使う、ちょっとした計算」のために、わざわざ 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)); のように出力するのを忘れずに。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。

次のレッスンへ →