第25回です。
前回はこちら。
[第25回の様子]
2022/01/12に第25回を開催した。
内容としてはRust By Example 日本語版の「9.2. クロージャ」、「9.2.1. 要素の捕捉」に取り組んだ。
参加者はたしか5人。2022年もこのくらいの人数でちまちまと続けていけるといいな...。
[学んだこと]
- 9.2. クロージャ
- クロージャを使うと、周りの変数を参照しつつ、その場限りの関数を作ることができる
- 基本的な文法は以下のとおり
|val| val + x
:|val|
が引数で、戻り値がval + x
になる。- 関数本体の式が1つだけの場合は関数本体を
{ }
で囲まなくてよい(囲んでもよい) - 引数や戻り値の型は推論させて省略可能
- 関数で書く場合と比較するとこうなる
- 関数バージョン:
fn function(i: i32) -> i32 { i + 1 }
- 型指定ありクロージャ:
let closure_annotated = |i: i32| -> i32 { i + 1 };
- 型指定なしクロージャ:
let closure_inferred = |i| { i + 1 };
- 呼び出す際は関数と同じで
closure_annotated(1)
のように書く - 引数なしの場合は以下のようになる
let one = || 1; println!("ans: {}", one()); // ans: 1と出力される let two = || { 2 }; println!("ans: {}", two()); // ans: 2と出力される
- 9.2.1. 要素の捕捉
- 型アノテーションが不要なので、クロージャは外側の要素(変数)を柔軟に取得できる
- 以下は借用してリファレンスを保持する場合
let color = String::from("green"); // `color`を借用(`&`)し、その借用とクロージャを`print`変数に保持 // 借用は`print`がスコープから出るまで続く。 let print = || println!("`color`: {}", color); print(); // `color`: greenと出力される
- printが参照しているので、所有権の移動が発生するとコンパイルエラーとなる
let color = String::from("green"); let print = || println!("`color`: {}", color); let _color_moved = color; print(); // 以下のコンパイルエラー error[E0505]: cannot move out of `color` because it is borrowed --> src/main.rs:29:24 | 17 | let print = || println!("`color`: {}", color); | -- ----- borrow occurs due to use in closure | | | borrow of `color` occurs here ... 29 | let _color_moved = color; | ^^^^^ move out of `color` occurs here 30 | print(); | ----- borrow later used here
- 所有権がうつらない形で参照するだけなら問題ない
let color = String::from("green"); let print = || println!("`color`: {}", color); let _reborrow = &color; print(); // `color`: greenと出力される
- ミュータブルなクロージャを作成する場合には
mut
キーワードが必要になる
let mut count = 0; let mut inc = || { count += 1; println!("`count`: {}", count); }; inc(); // `count`: 1 inc(); // `count`: 2 // クロージャがもう利用していないので借用しても問題ない let _count_reborrowed = &mut count;
- mutableなクロージャが利用している変数を途中でimmutableに借用することはできない
let mut count = 0; let mut inc = || { count += 1; println!("`count`: {}", count); }; let _reborrow = &count; inc(); // 以下のコンパイルエラー error[E0502]: cannot borrow `count` as immutable because it is also borrowed as mutable --> src/main.rs:58:21 | 47 | let mut inc = || { | -- mutable borrow occurs here 48 | count += 1; | ----- first borrow occurs due to use of `count` in closure ... 58 | let _reborrow = &count; | ^^^^^^ immutable borrow occurs here 59 | inc(); | --- mutable borrow later used here
- コピーできない値をとるクロージャの場合、一度しか呼び出せない
// コピーできない値 let movable = Box::new(3); // `mem::drop`は`T`(ジェネリック型)を取るため、このクロージャは参照ではなく値を取る。 // コピー可能な値ならば、元の値はそのままでコピーのみを取る。不可能ならば値そのものを移動させる。 let consume = || { println!("`movable`: {:?}", movable); mem::drop(movable); }; consume(); // consume(); // 2回目に呼び出そうとすると以下のコンパイルエラー error[E0382]: use of moved value: `consume` --> src/main.rs:85:5 | 84 | consume(); | --------- `consume` moved due to this call 85 | consume(); | ^^^^^^^ value used here after move | note: closure cannot be invoked more than once because it moves the variable `movable` out of its environment --> src/main.rs:79:19 | 79 | mem::drop(movable); | ^^^^^^^ note: this value implements `FnOnce`, which causes it to be moved when called --> src/main.rs:84:5 | 84 | consume(); | ^^^^^^^
move
キーワードを利用すると変数の所有権をクロージャに移せる
let haystack = vec![1, 2, 3]; // haystackの所有権がクロージャにうつる let contains = move |needle| haystack.contains(needle); println!("{}", contains(&1)); // trueが出力される // 以下のようにhaystackを利用しようとするとコンパイルエラー println!("There're {} elements in vec", haystack.len()); error[E0382]: borrow of moved value: `haystack` --> src/main.rs:10:45 | 3 | let haystack = vec![1, 2, 3]; | -------- move occurs because `haystack` has type `Vec<i32>`, which does not implement the `Copy` trait 4 | 5 | let contains = move |needle| haystack.contains(needle); | ------------- -------- variable moved due to use in closure | | | value moved into closure here ... 10 | println!("There're {} elements in vec", haystack.len()); | ^^^^^^^^^^^^^^ value borrowed here after move
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
クロージャの型推論便利そうだけど気をつけないと間違えそう...mutableは特に。
今週のプルリクエストはこちら。