rust從入門到放棄(五):所有權

rust從入門到放棄(五):所有權

從這篇文章開始就進入rust 比較特殊的部分了,這個和其他最大差異的地方。為啥rust 會引入所有權呢,這裡就涉及程式的記憶體管理了。程式只要執行就會一直申請記憶體,使用完畢後就會釋放記憶體。申請記憶體基本都是程式主動操作,譬如java或者c++ new 一個物件,回收的記憶體的方式有兩種,一直是類似 c 語言系列的,程式自己透過 free 函式釋放記憶體,另一種是類似 go和java 使用gc 垃圾回收,自動回收記憶體。他們各有優缺點。

今天我們來看看第三種記憶體管理方式:所有權。當我們在程式裡面建立兩個變數,分別是 i32型別 和 String型別,他們的記憶體排布如下:

rust從入門到放棄(五):所有權

可以看到基礎型別都是直接在棧上面分配的,而複雜型別的資料”hello“是放在堆上的,資料的所有權 s 是放在棧上的。我們都知道,棧的記憶體是不需要回收的,隨著程式的執行自動壓入和彈出的。這就意味著,rust 的堆記憶體,是透過棧管理的。

下面看一個有趣的例子

fn main() { let s = String::from(“hello”); let s1 = s; println!(“{}”, s);}

這段程式碼在其他語言看來再平常不過了,變數賦值。但這段程式碼在rust 是無法透過編譯的,因為函數里面第二行 s1 = s ,將所有權從 s 轉移到了 s1 了,就像一個物品一樣,張三把它送給了李四,那麼後面張三再想使用這個物品的時候,就會報錯了。rust 之所以這樣設計就是為了避免記憶體的重複釋放,當程式執行到最後一行的時候,s 和 s1 從棧上面彈出的時候,他們指向記憶體就會被釋放,這樣就會出現釋放兩次的問題。

我們可以在create 函式內將所有權轉移出來,然後在consume 裡面消耗這個所有權。

fn create() -> String { let s = String::from(“hello”); return s;}fn consume(s: String) { println!(“{}”, s);}fn main() { let s = create(); consume(s);}

如果我們在main 最後一行列印 s 的一樣會報錯,因為所有權已經轉移(move)到 consume 方法裡面,並且在 consume 方法執行完的時候,已經被消耗掉了(釋放了)。整個流行如下所示

rust從入門到放棄(五):所有權

經過了上面的學習,我們對所有權有了基本瞭解。那麼我們看這段程式碼,你猜一猜會報錯嗎?

fn main() { let a = 3i32; let b = a; println!(“{}”, a);}

如果按照上面所有權轉移(move)的理論這裡的 a 是不是就不能用了。但這段程式並不會報錯,能夠正常執行,這又是為啥。這就是涉及到所有權的另外一個屬性複製(Copy)。還記得上一篇文章介紹trait 嘛,這裡的copy 其實rust 定義的 copy trait。凡是實現了 copy trait 的型別,在賦值的時候都是複製賦值,就是再建立一個內容相同的物件。他們在棧上的位置如下

rust從入門到放棄(五):所有權

那麼你會好奇,哪些型別實現了copy trait 呢?rust裡面基本型別(簡單型別),比如數字、字元、bool等都實現了copy trait。他們在賦值的時候都是複製一份。如果賦值型別,預設是move語義。比如下面的程式碼

struct Foo { data : i32}fn main() { let v1 = Foo { data : 0 }; let v2 = v1; //所有權轉移 println!(“{:?}”, v1。data);}

上面的程式碼會編譯報錯,因為v1 的所有權已經轉移給了v2,後面就不能再使用v1了。我們可以透過對Foo 實現copy trait 來實現複製語義。

impl Clone for Foo { fn clone(&self) -> Foo { Foo { data : self。data } }}impl Copy for Foo {} //實現 copy traitfn main() { let v1 = Foo { data : 0 }; let v2 = v1; println!(“{:?}”, v1。data);}

這裡有個特殊點,實現copy trait 需要額外實現了一個clone 的trait 。因為copy trait 繼承了 clone trait。

pub trait Copy: Clone { // Empty。}

所以這裡需要實現clone trait。如果每個strut 都去實現這種機械化的 copy trait 是低效的,為此,rust 提供了 derive,我們將copy 和clone 加到strut 後,也可以實現上面相同的效果。

#[derive(Copy, Clone)]struct Foo { data : i32 }

這個derive 感覺和 java 或者 Python 裡面的註解類似,幫助我們生成一些公共程式碼,從而提高開發效率。