邁向 Rust

共享借用與獨占借用的規則(shared vs exclusive borrow)

借用附帶一條規則,幾乎扛起了大部分的安全工作,值得用最白話的方式陳述:在任一時刻、對某一份資料,你可以擁有任意數量的共享(唯讀)借用,或剛好一份可變(可讀寫)借用——但兩者絕不同時並存。人們把它縮寫成「一可變 XOR 多共享」,或「別名化 XOR 可變性」。許多讀者,可以。一個寫者,可以。寫者與任何其他讀者或寫者同時存在,禁止。

為什麼要這麼尖銳的規則?因為幾乎每個討厭的資料錯誤,都來自程式的某一部分在另一部分還盯著某樣東西時去更動它。在 C 裡這是微妙別名化錯誤的來源,跨執行緒時則是資料競爭——兩條執行緒碰同一塊記憶體、至少一條在寫、彼此之間又沒有先後順序。Rust 從構造上就消滅了這整個家族。共享參考 &T 保證你持有它期間借來的資料不會改變,這正是為什麼可以同時有許多份。可變參考 &mut T 保證此刻只有你能看見這份資料,這正是為什麼可以放心讓你更改它。因為兩者無法共存,任何讀者都不可能觀察到一個寫到一半的狀態,任何兩個寫者也不會互相覆蓋。這對單執行緒程式(沒有意外的別名化)與多執行緒程式(沒有資料競爭)都是同一份保證。

具體來說,編譯器會追蹤每個值當下存活的借用。看 let r1 = &v; let r2 = &v;——兩份共享借用,允許。看 let m = &mut v;——一份可變借用,允許,但前提是 v 沒有任何共享借用仍存活。若你想在 &v 還在使用時造出 &mut v,編譯器會拒絕:無法將 v 借為可變,因為它同時被借為不可變。它一開始感覺很嚴格,初學者確實會跟它搏鬥。但這條規則不是任意的官僚作風——它正是編譯器能在不靠任何執行期檢查、不靠垃圾回收的前提下,保證你的參考永遠不會看到損壞或已釋放資料的那個精確條件。

let mut v = vec![1, 2, 3]; let a = &v; // 共享借用 let b = &v; // 又一份共享借用 -- 可以 println!("{a:?} {b:?}"); let m = &mut v; // 獨占借用 -- 此時可以(a、b 已不再使用) m.push(4); // let c = &v; // 若放在 m 仍存活時,這裡會錯誤

許多共享借用可以並存;可變借用則要求它是唯一存活的借用。

這條規則是資料競爭為何發生的編譯期版本:寫者與任何其他存取重疊,正是危險的情形,而 Rust 在安全程式碼中禁止它。它也適用於單一執行緒之內,不只是跨執行緒。

又稱
aliasing XOR mutabilityone mutable or many shared&T vs &mut T共享 XOR 可變一可變或多共享