Rust 學習筆記:所有權

October 3, 2023 · Chinese


前言

本系列文書寫我個人初探 Rust 的學習筆記,章節劃分主要基於著名的 The BookThe Rust Programming Language,程式碼部分通常是個人閱讀消化後以方便說明的方式撰寫,完整學習建議直接參見該書。

The Rust Programming Language

該書也有中文翻譯版,不過個人閱讀以英文原版為主以鞏固對 terminology 的一致認識,我認為對未來閱讀以及查找資料會較為順暢。

不論語言該書都是相當優秀的學習資源,選擇適合你的語言開始學習 Rust 吧。

Appendix F: Translations of the Book

所有權 Ownership

所有權是 Rust 這門語言最重要的特色,也是 Rust 能做到 memory safety 的核心。理解所有權對學習 Rust 至關重要,但其特殊性又是 Rust 的學習曲線陡峭的原因之一。

記憶體使用

要理解所有權,首先就要把計概學過的觀念拿出來複習 XD,程式在使用記憶體時會使用到兩種資料結構,堆疊(stack)與堆積(heap),因為我覺得他們的中文很容易混淆我習慣還是使用英文。

關於 stack 跟 heap 的概念大學的計概或資料結構課程都會詳細學習,沒有學過的可以往這方面去學習我就不說明了。

簡單來說

  • 要推入 stack 的資料必須是 fixed size,且在 compile time 已知大小。
  • dynamic size 的資料必須存入 heap,memory allocator 配置記憶體空間後會回傳 pointer,stack 上只會儲存 pointer 與一些 metadata (length, size..)

因為配置在 heap 上的資料(或記憶體空間) 不會和 stack 中的資料一樣在函式結束後 pop 出去,如何追蹤 heap 上的資料控制何時應該釋放記憶體空間就是程式語言需要處理的課題。

在許多內建 garbage collector 的語言中 (通常是高階語言),GC 會自行去追蹤並釋放記憶體空間,對開發者來說是最方便的,不需要去煩惱記憶體的問題,但也會有效能較差、無法自己控制記憶體的缺點,GC 的存在也讓程式顯得笨重。

另一種方式是全權交給開發者自行控制 (如 C),最基礎的用法要在需要配置記憶體時手動 malloc 一塊記憶體,並且在使用完畢後 free 釋放掉記憶體空間,這樣完全手動控制雖然能夠做到最佳化,卻又讓開發時間相應增加,另一方面也提高使用的難度,容易自己寫出問題。

Rust 的 Ownership 是管理記憶體的另一套方案,降低自己把程式寫壞的機率卻又不用 GC,其基本概念是遵循一套規則由 Rust 自動進行 memory 的配置與釋放,雖然不用手動管理記憶體但學習難度仍然不低,這套規則會讓撰寫時受到許多限制,這也是學習 Rust 的必經之路。

Copy vs. Move

下面是一段簡單的程式,也很容易理解,將 x 這個 integer 賦值給了 y,這邊做的是一份 copy,xy 都有一份整數 5

let x = 5;
let y = x;
println!("{}", x);  // 5
println!("{}", y);  // 5

到了 String 這樣大小不定的東西行為就不同了,String 的值需要儲存在 heap 上。賦值時只會 copy 一份指向 heap 上 String 的參考,而不會將整個 String 的資料複製過來。

然而運行下面的程式會發現報錯,這邊我們透過 error message 看到兩個新的概念, borrow 以及 move。

let s1 = String::from("test");
let s2 = s1;
println!("{}", s1);     // ERROR: borrow of moved value: `s1`
println!("{}", s2);  

Move

在我們將 s1 賦值給 s2 時我們 copy 的是參考,這邊還算好理解,特別的是在 copy 的同時 Rust 會將 s1 的變數無效化,我們便不再能夠使用 s1,這個行為稱為 Move,其目的在於確保 memory safety。

Borrow

We call the action of creating a reference borrowing.

Borrow 的概念不難理解,顧名思義,借用並不會發生所有權的移轉,建立參考的行為在 Rust 中被稱為 borrowing。

回到上面的程式,s1 被移動到了 s2,在我們將它傳入函式時,因為 s1 已經被無效化,因此我們無法再借用 s1

所有權規則

First, let’s take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them:

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

Ownership Rules

到這邊我們可以總結所有權的規則,The Book 的規則如上。

我簡單的理解就是「每個值都會有且同時只有一個 owner,值會在離開作用域後被 drop 掉」。

函式

有關函式傳參的行為和賦值類似,會產生所有權的變動,函式可以利用回傳值進行所有權的返還或賦予。

另一種方式是使用參考,不涉及所有權的變動,會在之後的章節學習。