Rust語言記憶體管理之妙

_西門吹牛發表於2018-12-23

首發於我的rust 學習日記

規則

  • Rust 中的每一個值都有一個被稱為其 所有者(owner)的變數。
  • 值有且只有一個所有者。
  • 當所有者(變數)離開作用域,這個值將被丟棄。(Rust 在結尾的 }處自動呼叫 drop釋放記憶體)
  • 移動(堆記憶體變數),當值(s1)被賦值給另外一個變數(s2)後,rust則認為變數s1,不再有效。
  • 棧型別變數無移動的說法(沒有深淺拷貝的區別)
  • 將值傳遞給函式在語義上與給變數賦值相似

變數與資料的互動(move)

  • 將 5 繫結到 x;接著生成一個值 x 的拷貝並繫結到 y。現在有了兩個變數,x 和 y,都等於 5。因為正數是有已知固定大小的簡單值,所以這兩個 5 被放入了棧中(重點:兩個值都放入棧中了)。
    let x = 5;
    let y = x;
    複製程式碼
  • s1 和 s2 這兩個變數指向相同的地址(hello分配的堆記憶體)。
    let s1 = String::from("hello");
    let s2 = s1;
    複製程式碼
  • 圖1-1(s1 的記憶體引用圖)

Rust語言記憶體管理之妙

  • 圖1-2(s1和s2的引用圖(錯誤)其他語言js\go 是這樣的)

Rust語言記憶體管理之妙

  • 圖1-3(s1和s2的引用圖(正確)rust 是這樣的,s1賦值s2後,Rust 則認為 s1 不再有效)

Rust語言記憶體管理之妙

當值(s1)被賦值給另外一個變數(s2)後,rust則認為變數s1,不再有效(圖1-3)

  • Rust 在結尾的 }(作用域結束後) 處自動呼叫 drop(釋放記憶體)。
  • 如上圖,當 s2 和 s1 離開作用域,他們都會嘗試釋放相同的記憶體。這是一個叫做 二次釋放(是錯誤的)。
  • 為了確保記憶體安全,Rust 則認為 s1 不再有效,因此 Rust 不需要在 s1 離開作用域後清理任何東西,如下案例。
    fn main() {
        let s1 = String::from("hello");
        let s2 = s1;
    
        // 本行會報錯 value borrowed here after move
        println!("{}, world!", s1);
    }
    複製程式碼

克隆(深拷貝)

  • 實現如下圖,賦值變數的同時,進行資料拷貝的方案,rust也是支援的
  • 這段程式碼能正常執行,產生的變數記憶體如圖1-4
    fn main() {
        let s1 = String::from("hello");
        let s2 = s1.clone();
    
        println!("s1 = {}, s2 = {}", s1, s2);
    } 
    複製程式碼
  • 圖1-4

Rust語言記憶體管理之妙

棧型別變數無移動的說法(純拷貝)

  • x,y是編譯時確定大小的型別,因此整個儲存在棧上,所以拷貝其實際的值是快速的。因此rust對棧變數進行純拷貝,便不會造成效能的影響。
  • 也就意味著建立變數 y 後, 沒必要使x 無效。
let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);
// x = 5, y = 5
複製程式碼

將值傳遞給函式在語義上與給變數賦值相似

fn main() {
    let s = String::from("hello");  // s 進入作用域

    run_move(s);                    // s 的值移動到函式裡,s失效
                                    // 因此到這裡,s不再有效
    /* 
    將會報錯:因為s已經被move
    報錯資訊:will error value borrowed here after move
    println!("s:{}",s);            
    */
    let x = 5;                      // x 進入作用域

    run_copy(x);                    // x 應該移動函式裡
    println!("x:{}",x);             // 因為 x 是 棧變數,因為不會被 move 使失效

} 
//  x 移出了作用域,
//  s 移出了作用域但,因為 s 的值已被移走,所以不會有特殊操作

fn run_move(some_string: String) { // some_string 進入作用域
    println!("run_move:{}", some_string);
} // 這裡,some_string 移出作用域並呼叫 `drop` 方法。佔用的記憶體被釋放

fn run_copy(some_integer: i32) { // some_integer 進入作用域
    println!("run_copy:{}", some_integer);
} // some_integer 移出作用域
複製程式碼

相關文章