Rust 程式設計影片教程對應講解內容-所有權

linghuyichong發表於2019-12-19

頭條地址:https://www.ixigua.com/i676544267458235648...
B站地址:https://www.bilibili.com/video/av78062009?...
網易雲課堂地址:https://study.163.com/course/introduction....

  1. 所有權是rust語言中最與眾不同的特性,它讓rust無需垃圾回收就可以保證記憶體安全。

  2. 計算機語言對記憶體的管理有三種方式:

    • 程式設計師自己分配和回收,例如:c/c++。
    • 語言自帶GC,例如go語言。
    • rust語言為第三種方式,透過所有權系統管理記憶體,編譯器在編譯時根據規則對記憶體使用進行檢查。

3. 棧和堆

  • 棧的操作是先進先出,儲存於棧上的資料編譯時都佔用已知固定的大小。分配方式:直接分配
  • 編譯時大小未知或者是變化的資料在堆上進行儲存。 表示方式:用指標和大小表示

4. 變數作用域
一對花括號表示的範圍,就是作用域,如下:

{
let i = 1;   --------------------------------|
...                                                   |
let j = 2; -------|                               |---i的作用域
...                   |------j的作用域         |
...----------------|--------------------------|  
}

說明:

  • 變數進入它的作用域有效
  • 離開作用域後無效
  1. 對於String型別,如:
    let s = String::from("hello"); 
    s.push_str(", world");

    對於s,其所需要記憶體大小是變化的,因此需要在堆上進行分配。
    對於在堆上分配的記憶體,程式語言一般有幾種回收方式:

    • 有GC的語言自動回收,如go、java
    • 程式設計師手動回收,如c/c++
    • rust語言採用:當變數離開它的作用域後,就自動釋放。

因此,在c語言中:

{
    let s = String::from("hello");
    ...
} //自動釋放s,s不再有效

這是一個將 String 需要的記憶體返回給作業系統的很自然的位置:當 s 離開作用域的時候。當變數離開作用域,Rust 為我們呼叫一個特殊的函式。這個函式叫做 drop,在這裡 String 的作者可以放置釋放記憶體的程式碼。Rust 在結尾的 } 處自動呼叫 drop。

  1. 移動
    例子1:
    let x = 5;  //將5繫結到x
    let y = x;  //生成一個x的複製繫結到y

    由於都是確定的型別,所以在存放在棧中。

例子2:

let s1 = String::from("hello");  //s1在堆上
let s2 = s1;    //移動s1到s2,此後不能再使用s1
println!("{}, world!", s1); //此處要報錯,s1不能再使用

原因:

  • 兩者的存放方式,畫圖 是淺複製
  • 如果s1繼續有效,那麼釋放的時候(呼叫drop)會釋放兩次,會發生錯誤,因此這裡s1不能再使用
    說明:rust語言預設是淺複製,同時將被複製的變數就會變得無效。
  1. 克隆
    如果要複製堆上的內容,則使用clone,例子:

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2);
  2. 在棧上的資料複製
    例如:

    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);  //ok,x,y都在棧上

Rust 有一個叫做 Copy trait 的特殊註解,可以用在類似整型這樣的儲存在棧上的型別上。如果一個型別擁有 Copy trait,一箇舊的變數在將其賦值給其他變數後仍然可用。Rust 不允許自身或其任何部分實現了 Drop trait 的型別使用 Copy trait。
常用的具有Copy trait的有:

  • 所有整數型別,比如 u32。
  • 布林型別,bool,它的值是 true 和 false。
  • 所有浮點數型別,比如 f64。
  • 字元型別,char。
  • 元組,當且僅當其包含的型別也都是 Copy 的時候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
  1. 函式與作用域
    例子1:
    fn main() {
    let s = String::from("hello");  // s 進入作用域
    takes_ownership(s);             // s 的值移動到函式里 ...
                                                 // ... 所以到這裡不再有效
    let x = 5;                              // x 進入作用域
    makes_copy(x);                  // x 應該移動函式里,
                                               // 但 i32 是 Copy 的,所以在後面可繼續使用 x
    } // 這裡, x 先移出了作用域,然後是 s。但因為 s 的值已被移走,所以不會有特殊操作
    fn takes_ownership(some_string: String) { // some_string 進入作用域
    println!("{}", some_string);
    } // 這裡,some_string 移出作用域並呼叫 `drop` 方法。佔用的記憶體被釋放
    fn makes_copy(some_integer: i32) { // some_integer 進入作用域
    println!("{}", some_integer);
    } // 這裡,some_integer 移出作用域。不會有特殊操作

例子2:

fn main() {
    let s1 = gives_ownership();         // gives_ownership 將返回值,移給 s1

    let s2 = String::from("hello");     // s2 進入作用域
    let s3 = takes_and_gives_back(s2);  // s2 被移動到takes_and_gives_back 中, 它也將返回值移給 s3
} // 這裡, s3 移出作用域並被丟棄。s2 也移出作用域,但已被移走,所以什麼也不會發生。s1 移出作用域並被丟棄
fn gives_ownership() -> String {             // gives_ownership 將返回值移動給呼叫它的函式
    let some_string = String::from("hello"); // some_string 進入作用域.
    some_string                              // 返回 some_string 並移出給呼叫的函式
}
// takes_and_gives_back 將傳入字串並返回該值
fn takes_and_gives_back(a_string: String) -> String { // a_string 進入作用域
    a_string  // 返回 a_string 並移出給呼叫的函式
}

總結: 將值賦給另一個變數時移動它。當持有堆中資料值的變數離開作用域時,其值將透過 drop 被清理掉,除非資料被移動為另一個變數所有。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
令狐一衝

相關文章