跳轉到

2.2 所有權:Rust 的核心#

這是 Rust 最重要的概念#

本書 1.2 節說過,Rust 的安全建立在所有權上。這一節把最核心的規則講清楚——理解了這一節,80% 的「為什麼編譯不過」都會有答案。

三條規則#

  1. 每個值在任一時刻都有唯一的主人(owner)
  2. 主人離開作用域時,值自動釋放(呼叫 drop)
  3. 賦值與傳參預設是「搬移(move)」——所有權轉移,原變數失效

搬移(Move):預設行為#

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;                 // 所有權從 s1 搬到 s2
    // println!("{}", s1);       // ← 編譯錯誤!s1 已經不是主人了
    println!("{}", s2);          // ✓
} // s2 離開作用域,記憶體在這裡釋放(且只釋放一次)

為什麼要這樣設計?String 的資料存在堆積(heap)上,如果 s1 和 s2 都當自己是主人,作用域結束時會釋放兩次(double free,C/C++ 的經典錯誤)。Rust 的解法:搬移之後原主人直接失效,double free 在語言層面不可能發生。

Copy:小型型別的例外#

整數、浮點數、布林、字元等存在堆疊上的小型型別,賦值時是複製(Copy)而非搬移:

let a = 5;
let b = a;        // Copy:a 仍然有效
println!("{} {}", a, b);   // ✓

判斷法:有堆積資料的型別(String、Vec、HashMap)是 Move;純堆疊型別(整數、float、bool、char)是 Copy。 編譯器會告訴你,不用背。

借用(Borrow):不拿所有權,只是參考#

如果每次傳參都要把所有權搬走,用一次就不能再用,太不方便。解法是借用:用 & 表達「我只是借用,不擁有」:

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

    let len = calculate(&s);   // 借用 s,不拿走所有權
    println!("{} 長度 {}", s, len);  // ✓ s 還能用
}

fn calculate(s: &String) -> usize {
    s.len()
} // s 是借用,離開作用域不會釋放資料(它不是主人)

借用有兩種:

不可變借用 &T 可變借用 &mut T
能讀嗎 ✓ ✓
能寫嗎 ✗ ✓
同時能有幾個 多個 只能一個

借用檢查器的兩條鐵律#

  1. 同一時間,要嘛多個不可變借用,要嘛一個可變借用——不能同時有
  2. 借用的壽命不能超過被借用者(資料主人)
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;        // ✓ 多個不可變借用
    println!("{} {}", r1, r2);

    let r3 = &mut s;    // ✓ 上面用完了(NLL),現在可以借用可變
    r3.push_str(" world");
    // let r4 = &s;  // 若同時還有 &mut s 在用,這裡就編譯錯誤
}

這兩條鐵律在編譯期消滅了資料競爭(data race)——兩個人同時改同一份資料的問題,在 Rust 裡編譯不過。這也是 Rust 號稱「fearless concurrency(無懼並行)」的根基。

適應期的建議#

  • 編譯錯誤時,認真讀錯誤訊息——Rust 的錯誤訊息出名的詳細,通常直接告訴你怎麼改
  • 被搬移之後還想用?要嘛 clone(s.clone(),明確的複製成本),要嘛改用借用
  • 前幾週覺得「編譯器在找麻煩」是正常的;幾週後你會發現自己主動設計出所有權清晰的程式

本章小結#

  • 三條規則:唯一主人、離開作用域自動釋放、賦值傳參預設 Move
  • 有堆積資料是 Move,純堆疊型別是 Copy(編譯器會告訴你)
  • 借用 &:不拿所有權,只用一下
  • 借用鐵律:多個不可變借用 XOR 一個可變借用——編譯期消滅資料競爭
  • 卡住時讀錯誤訊息,clone() 是明確的逃生口

想一想#

  1. 為什麼 let s2 = s1 之後 s1 不能用?這避免了什麼 C/C++ 的錯誤?
  2. 「多個讀者 XOR 一個寫者」這條鐵律,消滅了哪一類的並行錯誤?
  3. 什麼時候該用借用、什麼時候該搬移、什麼時候 clone?判斷標準是什麼?