Rust dojo第25回を開催した

第25回です。

前回はこちら。

kdnakt.hatenablog.com

 

 

[第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と出力される
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は特に。

 

今週のプルリクエストはこちら。

github.com