第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は特に。
今週のプルリクエストはこちら。