Rust 導引筆記系列 #06 -- 物件所有權 (Ownership)
從本篇開始,就要進入核心部份了。所有權的傳遞與借用是 Rust 能在記憶體安全性保證之下卻又不需要垃圾回收器 (Garbage Collector, GC) 的關鍵,也是讓效能足以與 C++ 批敵的特性。
溫馨提醒:本篇具有一些重要觀念,請帶著清楚的腦袋閱讀~~
值與變數與所有權 (Value & Variable & Ownership)
首先,我們需要建立一個觀念:變數和它所帶有的值是不同的兩種東西
變數就如同是一個容器,可以在裡面裝入數值(內容物)。當在讀取變數時,實際上是在讀取內容物的部份,因此,變數必須要有數值才可以讀取它。在一般使用上,也是如此。
在 Rust 中,每個數值(內容物)都對應到一個變數(容器),這個變數稱作「擁有者」(Owner);也就是每個數值只能被裝在一個變數裡。
當變數脫離它的作用域時,裡面的數值就會自動被捨棄 (dropped),這個變數也不再能被使用。
fn main() {
{
let lucky = 1003; // This variable is in scope
println!("My lucky number is: {}", lucky);
} // <--- The variable goes out of scope here!
//println!("My lucky number is: {}", lucky);
// It's invalid to use the variable here,
// and you will get a compile error if you do that!!!
}
記憶體分配 (Memory Allocation)
在程式中,往往會有一些類型需要動態配置記憶體。俗話說,「有借有還,再借不難」,這道理大家都知道,但有時就是會有意外或忘記的時候,就會造成所謂的記憶體洩漏 (Memory Leak)。
Rust 在變數脫離作用域時捨棄數值的時候,會對具有 Drop
特徵的物件,呼叫一個類似解構子 (destructor) 的函式,稱為 drop()
。如此一來,就可以在裡面將記憶體釋放,或是將一些清理的工作放在其中,搭配上其他所有權的規則,就能完美的避開這類問題了~
P.s. 關於特徵的部份未來會再說明…
字串 (String
)
String
是一種動態配置的字串物件,由於牽涉到記憶體分配,它也是具有 Drop
特徵的物件之一。和上面有87%像的範例:
fn main() {
{
let name = String::from("Leo"); // This variable is in scope
// And it also allocated some memory
println!("My name is: {}", &name);
} // <--- The variable goes out of scope here!
// drop() would be called before the value dropped.
// Thus, String can free the memory!!!
//println!("My name is: {}", &name);
// It's invalid to use the variable here,
// and you will get a compile error if you do that!!!
}
在字串變數脫離空間之後,就會執行 drop()
,將配置的記憶體釋放,再將堆疊上的資料釋放。
所有權轉移
在 Rust 中,數值的指派、傳遞都會使數值的所有權轉移到另一個變數上。
fn main() {
// String will transfer when reassigned
let name = String::from("Leo");
let name2 = name;
// Now, `name` lost its ownership.
// You can't use it anymore!
//println!("My name is: {}", &name);
// It's invalid to use the variable,
// But you can use the new variable with the ownership
println!("My name is: {}", &name2);
}
除了具有 Copy
特徵的型態會自動進行複製(整數、浮點數、布林…等基本型態)。其他的型態除非明確指示要複製它(前提是可被複製),否則都會發生所有權的轉移。
函式與所有權轉移
函數的參數與回傳也會導致所有權轉移的發生,直接看範例比較快:
fn print_string(s: String) -> String {
println!("The string is: {}", &s);
// Return the string here,
// it makes the ownership move back!
s
}
fn main() {
// String will transfer when reassigned
let str = String::from("My name is Leo, nice to meet you.");
let ret_str = print_string(str);
// Now, `name` gave its ownership to the function.
// You can't use here anymore!
// But we got the same data back, stored in `ret_str`
println!("{}", &ret_str);
}
我們建構了一個字串 str
,在將它傳入 print_string()
,這個函式時,所有權就轉移到其中的參數 s
了。但是在最後,可以發現到,它也把同一個字串傳回來了。在 main()
函式中用另一個變數儲存回傳值就可以將原字串取回來。
但是這樣的作法顯得相當累贅,顯然有比較好的方式可以完成這件事。在保留所有權的狀態下,讓其他函式或程式碼使用某個數值就會需要參照 (Reference) 了。就留到下一篇再說吧~
上一篇:#05 -- 基本資料型態 II
下一篇:#07 -- 借用與參照