Rust的一大特色(甚至在官方教程中 被稱爲the most unique feature )就是其借用及所有權機制。這兩個機制由編譯器進行強制,並且可以極大限度地保證變量安全(並行安全)。
所有權
前面說過,同C/C++一樣,Rust中區分堆和棧(但不直接提及)。C/C++中語義上禁止從函數中返回指向局部變量的指針,是因爲當離開函數時,棧會被回收,所以局部變量也會被回收,導致指針失效。
在C/C++中,該禁止並非語法上的禁止。而在Rust中,所有權機制將該概念顯式表達,編譯器會拒絕編譯不滿足所有權機制的代碼。並且Rust設計者們希望通過所有權機制讓程序員不需要考慮堆棧,只需要考慮所有權即可(應當是出於一致性以及或許對未來自動優化的考量?)。
Rust中的 壽元 機制也涉及堆棧的區別。 兩者共同使得Rust不需要GC也可以保證內存安全。
所有權機制實際上就是三條規則:
每個值具有唯一的所有者變量
在同一時刻,所有者唯一
所有者離開作用域時,值會被丟棄
或者說(我個人的記法):每個值在每個時刻只有唯一的所有者,且有效性跟隨所有者。
所有權本身不涉及特殊語法,只要記住上面三條規則即可。 官方教程所有權規則部分 中有例子可供參考。
切換變量時的所有權轉移及保留
當將一個變量的值傳給另一變量時(如賦值或函數調用時),有可能涉及所有權的變更。 Rust中與此相關的是move(移動語義)、copy(淺複製)和clone(深複製)。
和C++11中move的結果一樣,Rust中將變量 a
傳給另一變量 b
時,會將變量 a
中的值移動到變量 b
中去。這時,值的所有權從變量 a
轉移到了變量 b
。該行爲在賦值( a=b
)或函數調用傳參時會自動發生,除非其右值符合copy或clone的條件(見下)。
當一個類型實現了 Clone
trait時,Rust會對其進行深複製。
當一個類型實現 Copy
trait時,Rust會對其進行淺複製。注意實現 Copy
trait需要該struct的所有成員也都實現了 Copy
,且該struct不能實現 Drop
trait(以保證不會double freeing)。 官方文檔的棧上數據複製部分 中對哪些類型實現了 Copy
trait進行了總結,且對於基本類型來說: iXX
、fXX
、bool
、 char
均實現了 Copy
(且元素均實現 Copy
的元組 ()
也實現 Copy
)。
引用
Rust中的引用類似C/C++中的指針,但使用 &
作爲其標記(如 &i32
);在其上調用方法或訪問成員不需要使用 ->
而照常使用 .
即可(事實上Rust中沒有 ->
這個操作符)。
當然,也可以認爲Rust中的引用類似於Java/Python中的引用,只是類型並非原數據類型而是原數據類型的引用類型,且在明確需要原數據類型時需要使用 *
來解引用。但這樣需要額外再解釋棧對其影響,反而更麻煩。
因爲類似指針,所以可以對引用繼續進行引用 &&
。不同於C/C++,在使用一個引用類型時,Rust編譯器會自動嘗試加入足夠量的解引用操作,以便操作可以正確執行(顯然,該機制只是爲方便而存在,不會涵蓋更複雜的情況)。
由於Rust變量默認爲不可變,在涉及引用時會有更細緻的設定: 引用類型的變量 的可變性、 引用本身 的可變性和 該變量所引用變量 的可變性是相互獨立的。
let mut a = 1; //a: i32 let b = &a; //b: &i32 *b = 0; //非法:因爲b是不可變引用(雖然a本身可變) let mut x = 2; //x: mut i32 let y = &mut x; //y: &mut i32 *y = -1; //合法:由於y是可變引用,所以可以修改 let i = 3; //i: i32 let j = 4; //j: i32 let mut k = &i; //mut k: &i32 k = &j; //合法:因爲k本身可變(無關i的可變性) let m = 5; //m: i32 let n = &mut m; //非法:不允許對不可變變量進行可變引用
借用
借用機制通過引用來實現,是對所有權機制的擴充。可以認爲Rust中的借用和引用是同一機制的兩個名稱,分別強調不同的側面:
- 當談及所有權時,稱之爲 借用
- 當談及數據類型時,稱之爲 引用
例如:函數參數爲 引用 類型這一現象被稱爲 借用 。
另外,借用/引用還與 壽元 機制相關。
在引用的基本原理之外,借用添加了額外的約束:
- 同一時刻只能有多個不可變借用或一個可變借用
- 借用必須始終有效
這些約束均是爲了保證變量和內存安全,避免數據競爭和訪問無效內存。
上面引用的各個例子有意避免了違反借用的規則。如下的代碼違反借用規則:
let mut a = 1; //x: mut i32 let b = &a; //y: &i32 let c = &a; //c: &i32 let d = &mut a; //非法
可以使用明確的作用域區分來解決該問題:
let mut a = 1; //x: mut i32 { let b = &a; //y: &i32 let c = &a; //c: &i32 } //b和c均已離開作用域,借用消失 let d = &mut a; //合法
您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。