1. 程式人生 > >內存安全

內存安全

需要 safety ica ext 空間 ack cos 情況 其它

Rust推崇安全與速度至上,它沒有垃圾回收機制,卻成功實現了內存安全 (memory safety)。

所有權

在Rust中,所有權 (ownership) 系統是零成本抽象 (zero-cost abstraction) 的一個主要例子。 對所有權的分析是在編譯階段就完成的,並不帶來任何運行時成本 (run-time cost)。 默認情況下,Rust是在棧 (stack) 上分配內存,對棧上空間變量的再賦值都是復制的。 如果要在堆 (heap) 中分配,必須使用盒子來構造:

let x = Box::new(5);

其中Box::new()創建了一個Box<i32>來存儲整數5,此時變量x

具有該盒子的所有權。 當x退出代碼塊的作用域時,它所分配的內存資源將隨之釋放,這是編譯器自動完成的。

考慮下面這段代碼:

fn main() {
    let x = Box::new(5);

    add_one(x);

    println!("{}", x);
}

fn add_one(mut num: Box<i32>) {
    *num += 1;
}

調用add_one()時,變量x的所有權也轉移 (move) 給了變量num。 當所有權轉移時,可變性可以從不可變變成可變的。函數完成後, num占有的內存將自動釋放。當println!再次使用已經沒有所有權的變量x

時, 編譯器就會報錯。一種可行的解決辦法是修改add_one()函數使其返回Box, 把所有權再轉移回來。更好的做法是引入所有權借用 (borrowing)。

借用

在Rust中,所有權的借用是通過引用&來實現的:

fn main() {
    let mut x = 5;

    add_one(&mut x);

    println!("{}", x);
}

fn add_one(num: &mut i32) {
    *num += 1;
}

調用add_one()時,變量x把它的所有權以可變引用借給了變量num。函數完成後, num又把所有權還給了x。如果是以不可變引用借出,則借用者只能讀而不能改。

有幾點是需要特別註意的:

  • 變量、函數、閉包以及結構體都可以成為借用者。
  • 一個資源只能有一個所有者,但是可以有多個借用者。
  • 資源一旦以可變借出,所有者就不能再訪問資源,也不能再借給其它綁定。
  • 生存期

    Rust 通過引入生存期 (lifetime) 的概念來確定一個引用的作用域:

    struct Foo<a, b> {
        x: &a i32,
        y: &b i32,
    }
    
    fn main() {
        let a = &5;
        let b = &8;
        let f = Foo { x: a, y: b };
    
        println!("{}", f.x + f.y);
    }

    因為結構體Foo有自己的生存期,所以我們需要給它所包含的域指定新的生存期‘a‘b, 從而確保對i32的引用比對Foo的引用具有更長的生存期,避免懸空指針 (dangling pointer) 的問題。

    Rust預定義的‘static具有和整個程序運行時相同的生存期,主要用於聲明全局變量。 由const關鍵字定義的常量也具有‘static生存期,但是它們會被內聯到使用它們的地方。

    const N: i32 = 5;
    
    static NUM: i32 = 5;
    static NAME: &static str = "David";

    其中類型標註是不可省略的,並且必須使用常量表達式初始化。 對於通過static mut綁定的變量,則只能在unsafe代碼塊裏使用。

    對於共享所有權,需要使用標準庫中的Rc<T>類型:

    use std::rc::Rc;
    
    struct Car {
        name: String,
    }
    
    struct Wheel {
        size: i32,
        owner: Rc<Car>,
    }
    
    fn main() {
        let car = Car { name: "DeLorean".to_string() };
    
        let car_owner = Rc::new(car);
    
        for _ in 0..4 {
            Wheel { size: 360, owner: car_owner.clone() };
        }
    }

    如果是在並發中共享所有權,則需要使用線程安全的Arc<T>類型。

    Rust支持生存期省略 (lifetime elision),它允許在特定情況下不寫生存期標記, 此時會遵從三條規則:

    • 每個被省略生存期標記的函數參數具有各不相同的生存期。
    • 如果只有一個輸入生存期 (input lifetime),那麽不管它是否省略, 這個生存期都會賦給函數返回值中所有被省略的生存期。
    • 如果有多個輸入生存期,並且其中有一個是&self或者&mut self, 那麽self的生存期會賦給所有被省略的輸出生存期 (output lifetime)。
  • 資源一旦以不可變借出,所有者就不能再改變資源,也不能再以可變的形式借出, 但可以以不可變的形式繼續借出。

內存安全