Rust 所有權(二):Move Data

  下面是一段正確的 Rust 代碼,定義變量 x 並賦值一個字符串,再將 x 賦值給變量 y,最後打印 y 會輸出字符串 “Hello, World!”:

fn main() {
    let x = String::from("Hello, World!");
    let y = x;
    println!("{}", y);  // 打印 y
}

  這段代碼的行爲和其它大多數編程語言無異。

  那麼,再來看看下面這段錯誤的代碼,它無法通過編譯:

// 編譯錯誤
fn main() {
    let x = String::from("Hello, World!");
    let y = x;
    println!("{}", x);  // 打印 x
    //             ^ value borrowed here after move
}

  這段代碼的行爲可就與大多數編程語言不同了。比如,在 JavaScript 語言中是允許這樣寫的:

// JavaScript
function main() {
  let x = 'Hello, World!';
  let y = x;
  console.log(x);
}

  問題出在哪?

  其實,編譯器的錯誤消息已經給出了答案,是因爲 “借用了已經移動(move)了的值 x”。暫且不管什麼是借用,只關注 move(數據移動)。數據移動是 Rust 中數據交互方式之一。在上例中,它指的是字符串 "Hello, World!" 的所有權從變量 x 移動到了變量 y。在移動之後,變量 x 變爲未初始化狀態,因此不允許再使用變量 x,除非給 x 再重新賦值。

  爲什麼要有 move 操作?

  因爲要遵守所有權規則。在同一時刻,一個值只能有一個所有者,即指向它的變量。上例中,第 3 行 "Hello, World!" 的所有者是變量 x,第 4 行  "Hello, World!" 的所有者是 y。反觀上例的 JavaScript 代碼,最後相當於  "Hello, World!" 的所有者有 2 個,即 x 和 y。

  只有唯一確定的所有者的好處是:內存安全。編譯器在編譯階段就可以確定在所有者出作用域時釋放內存資源,並自動生成管理內存的代碼。如果像 JavaScript 語言一樣是共享所有權的,那麼或者需要程序員對程序完全掌控,清楚地知道有多少個所有者、何時可以安全地釋放以及不能重複釋放等問題;或者採納 Linus 的建議,“選擇一門支持自動內存管理的編程語言”,例如 JavaScript。

  只有唯一確定的所有者的壞處是:操作麻煩。例如,有這樣一道算法題,要求刪除單鏈表中第 N 個結點。學過一些數據結構的同學能夠明白,這道題的關鍵點是修改被刪除結點前後的 next 指針,以及要考慮刪除首、尾結點這兩個特殊情況。如果用 Rust 來實現,那麼還額外需要在操作鏈表時保證所有權規則!在修改結點時,不允許出現一個結點有兩個所有者的情況!例如,下圖中紅框處出現的情形可能意味着 Node3 有兩個所有者,這是不允許的。

  數據 copy

  不是任何類型的數據都是非 move 不可的。如果有些數據不用 move 就能滿足所有權規則不會對程序產生明顯的影響並且簡單易操作,那麼何樂而不爲呢。看如下的代碼:

fn main() {
    let x = 666;
    let y = x;
    println!("{}", x);
    println!("{}", y);
}

  此例中的代碼是正確的,這也意味着這裏沒發生 move,否則打印 x 會報錯。這裏發生了數據 copy。數值 666 被拷貝了一份賦值給了 y。此時,x 和 y 分別保存了自己的數值 666,每個 666 都只有一個所有者。

  爲什麼 “Hello, World” 是 move,而 666 是 copy?

  因爲數值 666 在編譯時就已知其佔用的空間,並且不會改變。它被完全保存在棧內存上,可以快速地、安全地按位複製。所以它是 copy 的。字符串是稍微複雜的數據類型,字符串的長度在程序運行中是可以動態改變的。因此,字符串默認使用堆和棧內存來保存。它不能安全地按位拷貝,因爲會拷貝出多個指向堆中字符串的指針,違反了所有權規則。如果非要拷貝,必須進行 “深拷貝”,在 Rust 裏叫做 clone。

  我怎麼知道誰是 copy 的,誰是 move 的?

  最靠譜的方法是查看官方文檔,比如想知道 i32 是 copy 還是 move,就去查 i32 的文檔。在 Rust 裏擁有確定大小的標量類型是 copy 的,例如布爾類型、浮點型、整型等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/InjA92NjMB-Oz2Orh9lLCw