移動語意
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
前:
移至 s2
後:
將值傳遞至函式時,該值會指派給函式參數。這麼做會轉移所有權:
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
會取得專屬的獨立副本。- 當
s1
和s2
超出範圍時,皆會釋放自己的記憶體。
複製指派前:
複製指派後:
重要須知:
-
C++ 提供的選擇與 Rust 略有不同。由於
=
會複製資料,所以字串資料一定要完成複製。否則,假如其中任一字串超出範圍,就會導致重複釋放的結果。 -
C++ 也提供
std::move
,用於指出何時可以轉移特定值。例如假設是s2 = std::move(s1)
,就不會發生堆積分配的情形。轉移之後,s1
會處於有效但未指定的狀態。與 Rust 不同的是,程式設計師可以繼續使用s1
。 -
C++ 中的
=
可以依照要複製或轉移的型別來執行任何程式碼,這點與 Rust 不同。