本文へスキップ
BecomeCoder

Rustコース · 第5章 参照と借用 · レッスン21

借用のルール ― データ競合を防ぐ

ブラウザで完結

導入

前レッスンの「可変参照は同時に1つ」を含め、Rust の借用には明確なルールがあります。このルールこそが、マルチスレッドで悪名高いデータ競合(data race) を、コンパイル時に防ぐ核心です。

説明

借用のルールは次の2つに集約されます。ある時点で、あなたは次のどちらか一方だけを持てます。

  1. 不変参照(&T)は、いくつでも同時に持てる(みんなで読むだけなら安全)。
  2. 可変参照(&mut T)は、ただ1つだけ。しかもそのとき、他の参照(不変も可変も)は1つも持てない

言い換えると「読み手は何人いてもいいが、書き手がいるときは、その書き手ただ1人」。図書館の本を、みんなで読むのはOK、でも誰かが書き込んでいる最中は他の誰も触れない、というイメージです。

fn main() {
    let mut s = String::from("hello");

    // OK: 不変参照は何個でも
    let r1 = &s;
    let r2 = &s;
    println!("{r1} {r2}");     // r1, r2 の借用はここで終わる

    // OK: 上の不変参照を使い終わった後なら、可変参照を作れる
    let r3 = &mut s;
    r3.push_str("!");
    println!("{r3}");
}

これがエラーになる例です。

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;          // 不変参照(読み手)
    let r2 = &mut s;      // ← エラー! 読み手がいるのに書き手を作ろうとした

    println!("{r1} {r2}");
}

なぜこのルールが必要か——データ競合。データ競合とは、次の3つが同時に起きる状態です。

  • 2つ以上の場所から、同じデータにアクセスする。
  • そのうち少なくとも1つが書き込みである。
  • アクセスを調停する仕組みがない。

この状態は、結果が予測不能になったり、メモリを破壊したりする、非常に厄介なバグ(特にマルチスレッドで)を生みます。多くの言語では実行時にたまたま起きるまで気づけません。

Rust は借用ルールにより、「書き込む人がいるなら、その人以外は誰もアクセスできない」をコンパイル時に保証します。データ競合の3条件のうち「複数アクセス+書き込み」を成立させられないので、データ競合はコンパイルを通った時点で存在しません。これが Rust の掲げる「恐れないマルチスレッド(fearless concurrency)」の土台です(第10章で再訪します)。

graph TD
    root["同じ値への借用"]
    root --> read["不変参照 &T<br/>読むだけ → 何個でもOK"]
    root --> write["可変参照 &mut T<br/>書ける → ただ1つ・他の参照は禁止"]
    write --> safe["= データ競合が成立しない<br/>コンパイル時に保証"]

試すには

不変参照 &s と可変参照 &mut s を同時に有効にするとエラーになります。「読み手がいる間は書き手を作れない」を、エラーメッセージ(cannot borrow s as mutable because it is also borrowed as immutable)とともに確認してみましょう。厳しく感じますが、これが実行時のデータ競合を消してくれています。

実際に動かしてみよう

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

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

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