iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0

當電腦上的動態記憶體不再被使用時,應該要被釋放出來。每個語言都有自己管理記憶體的方式。

  • C/C++: 手動管理
  • Go/Java/Python: 使用Garbage Collection
  • Rust: Ownership

以下表格為比較各個記憶體管理機制的優劣:

Pros Cons
Garbage Collection - Error free - faster write time - No control over memory - Slower and unpredictable runtime performance - Large program size
Manual memory managemant - Control over memory - Faster runtime - Smaller program size - Smaller program size - Slower write time
Ownership model - Ownership model - Error free - Faster runtime - Smaller program size -Smaller program size

為什麼Rust不跟Python, Go一樣使用GC呢?Slower and unpredictable runtime performance是很重要的一個原因。
對於遊戲玩家來說,語言延遲是很討厭的一件事。Discord switch rust from go,從文章中可以得知,Discord的伺服器每兩分鐘會有一個spikes,而這個原因是Go的記憶體管理方式所造成的。

舉一個案例來說. GC/ rust/ c & c++ 三種處理回收機制就像是三種風格不同的健身房. GC就是有掃地阿姨的健身房, 你去就是拿器材放槓片, 用完就可以閃人, 反正阿姨會幫你把東西清掃乾淨. 但同一時刻阿姨打掃其實也會讓某些器材不能使用. c&c++ 比較像是沒有阿姨, 在進門的公告上寫明希望所有使用者要依照規定把槓片和器材搬回原處. 理論上不需要打掃時間, 也不用暫停使用某些器材. 但就是會有人不歸位, 久而久之就會到處亂放. Rust 比較像是一個異世界的健身房, 一但你沒有清楚交代你等一下槓片用多少該怎麼歸還, 你連推都推不起來,器材自行鎖住, 雖然沒有阿姨, 但基本上健身房可以保證持續運作.

Ownership(所有權)/Borrow(借用/引用)/Lifetime(生命週期)是Rust中非常重要的概念,也是Rust的基礎。
因為有些概念所以不需要Garbage collection來回收記憶體,並確保記憶體的安全性。

Ownership 的規則

  1. 每個數值(value)都會有一個變數作為它的擁有者(owner)
  2. 同時間只能有一個擁有者
  3. 當擁有者離開作用域時,數值就會被丟棄

先來看第一個例子:

fn main() {
  let a = 2;
  let b = a;
  println!("a: {} b: {}", a, b);
}

我們知道當一個數值所有權被轉移後,原來的變數就不能再使用該數值。然而有trait可以改變這個行為,它叫做Copy。簡單來說,當x = y發生時若y是可以被copy impl的話就會自動使用copy trait。
詳細關於trait

ownership-2

第二個例子:

fn main() {
    let a = String::from("hello");
    let b = a;
    println!("a: {} b: {}", a, b);
}

ownership-1

執行後卻發現編譯不過,原因是因為String的資料是儲存在Heap(堆積)上面而不是Stack(堆疊)上,並且String類型無法使用copy trait。

當Rust知道要賦予的數值是儲存在Stack上,就直接Copy一份到新的變數上。
但如果是Heap上Copy一份一樣的數據花的代價太大,所以當b要等於a的值的時候,賦予的其實是指向Heap資料的pointer。
還記得Ownership的第一條規則嗎?同時間只能有一個擁有者,"hello"這個數據的擁有者從a轉移到b身上,因此無法再繼續使用a變數了。

ownership-3

當變數傳到函式中也會發生一樣的行為。

fn main() {
    let a = String::from("hello");
    let b = test(a); // value "hello" move here
    println!("a: {}, b: {}", a, b); // borrow of moved value a
}
fn test(i: String) -> String {
    i
}
fn main() {
    let a = 32;
    let b = add_one(a);
    println!("a: {}, b: {}", a, b); // a: 32, b: 33
}
fn add_one(i: i32) -> i32 {
    i + 1
}

作用域

變數的作用域(scope),就是變數的有效範圍,在該作用域內宣告的變數就只能在該作用域使用,當離開作用域時變數就會被丟棄。

fn main() {
    let a = 32;
    {
        let b = 2;
    }
    println!("a: {}", a); // a: 32
    println!("b: {}", b); // cannot fund value b in this scope
}

if也是作用域的一個範圍

fn main() {
    let a = 32;
    if a > 1 {
        let b = 123;       
    }
    println!("a: {}", a); // a: 32
    println!("b: {}", b); // cannot fund value b in this scope
}

source:
Let's get rusty
Rust offcial doc


上一篇
[D3] 讓程式跑起來
下一篇
[D5] Lifetime
系列文
大閘蟹料理指南(rust)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言