Rust學習筆記/所有權+引用+借用

renyuneyun 2018年08月06日(週一) 1 mins

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進行了總結,且對於基本類型來說: iXXfXXboolchar 均實現了 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評論。