移動語意

An assignment will transfer ownership between variables:

fn main() {
    let s1: String = String::from("Hello!");
    let s2: String = s1;
    println!("s2: {s2}");
    // println!("s1: {s1}");
}
  • s1 指派給 s2 會轉移所有權。
  • When s1 goes out of scope, nothing happens: it does not own anything.
  • s2 超出範圍時,系統會釋放字串資料。

移至 s2 前:

StackHeaps1ptrHello!len4capacity4

移至 s2 後:

StackHeaps1ptrHello!len4capacity4s2ptrlen4capacity4(inaccessible)

將值傳遞至函式時,該值會指派給函式參數。這麼做會轉移所有權:

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
This slide should take about 5 minutes.
  • 請說明這與 C++ 中的預設情形相反:您必須使用 std::move,且已定義移動建構函式,系統才會根據值進行複製。

  • 只有擁有權才會轉移。是否產生任何機器碼來操控資料本身是一個最優化問題,而系統會主動將這些副本最優化。

  • 簡單的值 (例如整數) 可標示為 Copy (請參閱後續投影片)。

  • 在 Rust 中,克隆作業皆為明確設定,方法為使用 clone

say_hello 範例中:

  • 首次呼叫 say_hello 時,main 會放棄 name 的所有權。之後,name 就無法在 main 內使用。
  • name 配置的堆積記憶體會在 say_hello 函式結束時釋放。
  • 如果 main 以參照的形式傳送 name (&name),且 say_hello 能以參數的形式接受參照,main 就可以保留所有權。
  • 另外,main 可在首次呼叫 (name.clone()) 中傳遞 name 的克隆。
  • 在 Rust 中,移動語意為預設做法,且強制規定程式設計師必須明確設定克隆,因此不小心建立副本的可能性就會低於在 C++ 中。

探索更多內容

Defensive Copies in Modern C++

現代 C++ 可使用不同方式解決這個問題:

std::string s1 = "Cpp";
std::string s2 = s1;  // Duplicate the data in s1.
  • s1 的堆積資料會重複,s2 會取得專屬的獨立副本。
  • s1s2 超出範圍時,皆會釋放自己的記憶體。

複製指派前:

StackHeaps1ptrCpplen3capacity3

複製指派後:

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

重要須知:

  • C++ 提供的選擇與 Rust 略有不同。由於 = 會複製資料,所以字串資料一定要完成複製。否則,假如其中任一字串超出範圍,就會導致重複釋放的結果。

  • C++ 也提供 std::move,用於指出何時可以轉移特定值。例如假設是 s2 = std::move(s1),就不會發生堆積分配的情形。轉移之後,s1 會處於有效但未指定的狀態。與 Rust 不同的是,程式設計師可以繼續使用 s1

  • C++ 中的 = 可以依照要複製或轉移的型別來執行任何程式碼,這點與 Rust 不同。