導入
前レッスンの「可変参照は同時に1つ」を含め、Rust の借用には明確なルールがあります。このルールこそが、マルチスレッドで悪名高いデータ競合(data race) を、コンパイル時に防ぐ核心です。
説明
借用のルールは次の2つに集約されます。ある時点で、あなたは次のどちらか一方だけを持てます。
- 不変参照(
&T)は、いくつでも同時に持てる(みんなで読むだけなら安全)。 - 可変参照(
&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)とともに確認してみましょう。厳しく感じますが、これが実行時のデータ競合を消してくれています。