邁向 Rust

移動與 Copy 型別(move semantics)

所有權規則說每個值剛好有一個擁有者。但程式無時無刻不在把值從一個變數交給另一個——你做指派、你傳入函式、你回傳。那麼當你寫 let b = a 時,所有權會怎樣?在像 C 那樣的語言裡,複製一個指標只是多造出一個指向同一塊區塊的指標,於是兩個變數都以為自己在當家。Rust 不能在不破壞「唯一擁有者」規則的前提下允許這種事。它的答案是移動(move):當你指派或傳遞一個擁有堆積記憶體的值時,所有權被轉移到目的地,而來源被作廢——原本的變數再也不能使用。

想像一個 String,它是堆疊上一筆小小的記錄,存著指向堆積字元的指標、一個長度、一個容量。當你寫 let b = a 時,Rust 把那筆小小的堆疊記錄複製進 b,但它不複製堆積上的字元,而且關鍵地,它把 a 標記為不再有效。現在 b 是堆積資料的唯一擁有者,而 a 已死——再碰 a 一次,編譯器就會以「使用已移動的值」攔下你。這就是為什麼稱它為移動而非複製:所有權從 a 移動到了 b,留下剛好一個擁有者,因此堆積記憶體會在 b 的範圍結束時剛好被釋放一次。把值傳入函式也是以同樣方式移動它;這正是為什麼在所有權那條的範例裡,呼叫之後原本的變數就不能用了。

不過並非所有東西都會移動,這就是 Copy 型別登場之處。完全住在堆疊上、不擁有任何堆積資源的小型樸素值——整數、浮點數、bool、char、以及由這些東西組成的元組——實作了一個稱為 Copy 的特徵。對這些值而言,let b = a 會複製位元並讓 a 完好可用,因為複製它們很便宜,也沒有單一的堆積資源會讓兩個擁有者去爭。所以拇指法則是:若一個值擁有資源(像 String 或 Vec),指派或傳遞它會移動它並作廢來源;若它是簡單的 Copy 值(像 i32),它會被複製而來源繼續存活。誠實的微妙之處在於:移動是預設,Copy 才是特例——初學者第一次看到 String 在傳入函式後變得不能用,往往大吃一驚。

let a = String::from("hi"); let b = a; // 移動:現在 b 擁有它,a 被作廢 // println!("{a}"); // error[E0382]:使用已移動的值 `a` let x = 5; // i32 是 Copy let y = x; // 複製:x 仍可使用 println!("{x} {y}"); // 印出:5 5

String 的指派會移動並作廢來源;i32 的指派會複製並保留來源。

移動不會複製堆積資料,也不會對來源執行清理——它只是轉移所有權並禁止來源繼續被使用。若你真的想要 String 或 Vec 的第二份獨立副本,要用 .clone() 明確索取。

又稱
move semanticsmovemoving a value移動語意移動