&T 互斥 &mut T 紀律(aliasing XOR mutability)
C 與 C++ 裡最棘手的錯誤,多半源於同一種情境:兩段程式碼同時觸及同一塊記憶體,而其中至少一段在寫入。一個指標在另一條路徑改變它所指緩衝區大小時仍被使用;一個迭代器因為它底下的集合被改動而懸置;兩條執行緒競爭同一個變數。Rust 核心的安全規則用一條紀律一次攻擊所有這些情況:在任一時刻,一個值可以有許多份共享參考(&T)「或」恰好一份可變參考(&mut T),但絕不能兩者皆有。共享與可變互斥——這就是那個「互斥(XOR)」。
在機制上,借用檢查器把它當成對每個值的一條計數規則來執行:你可以同時發出任意數量的 &T(唯讀借用),因為許多讀取者彼此從不干擾;或者你可以發出單一一份 &mut T(讀寫借用),它是獨占的——只要它存活,就不能存在任何其他種類的、指向該值的參考,連共享的都不行。兩者不能重疊。因此一份 &mut T 是一個獨占存取的承諾:只要你持有它,沒有別的東西能在你背後觀察或更改這個值。這正是 C 程式設計師希望 restrict 修飾詞能處處給他們的那份保證,只不過 Rust 把它變成預設並加以檢查。這條規則正是讓安全 Rust 裡資料競爭不可能發生的原因:資料競爭需要兩條執行緒、其一在寫入、觸及共享資料——但寫入需要 &mut T,而依定義它無法與任何其他存取共存。
為何重要:別名與可變互斥「就是」健全性的根基;生命週期、Send/Sync、以及資料競爭的不存在,全都立基於它。它也是初學者最常對抗的規則,因為它禁止了一些感覺無辜的寫法——一邊把可變參考交給一個函式、一邊在別處仍讀取這個值,或在迭代一個串列時改動它。誠實的重點是:這不是 Rust 在龜毛;這是 Rust 拒絕了正是會造成無聲損毀的那種別名。當你真的需要共享可變(一張圖、一個共享計數器)時,你不會去打破規則——你把檢查搬到執行期,用內部可變性(Cell、RefCell、Mutex),那正是下一條的工作。
let mut v = vec![1, 2, 3]; let a = &v; // 共享借用 let b = &v; // 「另一個」共享借用:沒問題,都是唯讀 println!("{a:?} {b:?}"); let m = &mut v; // 獨占借用 // let c = &v; // error[E0502]:不能再以共享方式借用 `v` m.push(4); // 只有 `m` 能在它存活時碰 v
許多讀取者,或一個寫入者——絕不同時。正是這一條規則,讓安全 Rust 不可能有資料競爭。
這條紀律在「安全」Rust 裡成立。在 unsafe 內、用原始指標時,你可以對同一份資料造出兩個可變別名——而在不該如此時這麼做就是未定義行為,這正是 Stacked Borrows 與 Miri 存在要抓的東西。