@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

rustを味見する 7

今回はスレッドのレースコンディションを検出する例です。

use std::thread;
fn main() {
    let mut data = 100;
    thread::spawn(|| {
        data = 100;
    });
    thread::spawn(|| {
        data = 100;
    });
    println!("{}", data);
}

これをcargo runすると次のようにどどっとコンパイルエラーがでます。

PS C:\Users\led_l\rust_land\race> cargo run
   Compiling race v0.1.0 (C:\Users\led_l\rust_land\race)
error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function
 --> src\main.rs:4:19
  |
4 |     thread::spawn(|| {
  |                   ^^ may outlive borrowed value `data`
5 |         data = 100;
  |         ---- `data` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src\main.rs:4:5
  |
4 | /     thread::spawn(|| {
5 | |         data = 100;
6 | |     });
  | |______^
help: to force the closure to take ownership of `data` (and any other referenced variables), use the `move` keyword
  |
4 |     thread::spawn(move || {
  |                   ++++

error[E0499]: cannot borrow `data` as mutable more than once at a time
 --> src\main.rs:7:19
  |
4 |       thread::spawn(|| {
  |       -             -- first mutable borrow occurs here
  |  _____|
  | |
5 | |         data = 100;
  | |         ---- first borrow occurs due to use of `data` in closure
6 | |     });
  | |______- argument requires that `data` is borrowed for `'static`
7 |       thread::spawn(|| {
  |                     ^^ second mutable borrow occurs here
8 |           data = 100;
  |           ---- second borrow occurs due to use of `data` in closure

error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function
 --> src\main.rs:7:19
  |
7 |     thread::spawn(|| {
  |                   ^^ may outlive borrowed value `data`
8 |         data = 100;
  |         ---- `data` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src\main.rs:7:5
  |
7 | /     thread::spawn(|| {
8 | |         data = 100;
9 | |     });
  | |______^
help: to force the closure to take ownership of `data` (and any other referenced variables), use the `move` keyword
  |
7 |     thread::spawn(move || {
  |                   ++++

error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable
  --> src\main.rs:10:20
   |
4  |       thread::spawn(|| {
   |       -             -- mutable borrow occurs here
   |  _____|
   | |
5  | |         data = 100;
   | |         ---- first borrow occurs due to use of `data` in closure
6  | |     });
   | |______- argument requires that `data` is borrowed for `'static`
...
10 |       println!("{}", data);
   |                      ^^^^ immutable borrow occurs here

Some errors have detailed explanations: E0373, E0499, E0502.
For more information about an error, try `rustc --explain E0373`.
error: could not compile `race` due to 4 previous errors

エラー出過ぎなので、読んでいません。ですが・・・

レースコンディションそのものを静的解析でみつけるのは、ほぼ不可能です。 「スレッド間での値の共有をデフォルトで禁止する。共有可能な変数をスレッドに渡すこと自体をエラーとする」という作戦はRubyのractorと同じなので、馴染みがあります。

今回気になったのはthread::spawnというメソッドです。 クラスメソッド風に見えます。 名前空間付きの静的メソッドみたいなのがあるのでしょうか?

|| {
        data = 100;
    })

この書き方も馴染みがありません。 エラーメッセージをみると||の左にmoveを足してmove ||みたいな書き方をするようです。 所有権移転の書き方なのでしょうか?

サンプルプログラムをちょっと変えてスレッド内ではdata = 100;と実質的な値は変わらないようにしました。 エラーコードは変わらないようです。 やはりrustはソースコードの振る舞いをチェックしているわけではなく、文法をチェックしているようです。