Rust入坑指南:智能指針

在瞭解了Rust中的全部權、全部權借用、生命週期這些概念後,相信各位坑友對Rust已經有了比較深入的認識了,今天又是一個連環坑,咱們一塊兒來把智能指針刨出來,一探究竟。html

智能指針是Rust中一種特殊的數據結構。它與普通指針的本質區別在於普通指針是對值的借用,而智能指針一般擁有對數據的全部權。在Rust中,若是你想要在堆內存中定義一個對象,並非像Java中那樣直接new一個,也不是像C語言中那樣須要手動malloc函數來分配內存空間。Rust中使用的是Box::new來對數據進行封箱,而Box<T>就是咱們今天要介紹的智能指針之一。除了Box<T>以外,Rust標準庫中提供的智能指針還有Rc<T>Ref<T>RefCell<T>等等。在詳細介紹以前,咱們仍是先了解一下智能指針的基本概念。設計模式

基本概念

咱們說Rust的智能指針是一種特殊的數據結構,那麼它特殊在哪呢?它與普通數據結構的區別在於智能指針實現了DerefDrop這兩個traits。實現Deref可使智能指針可以解引用,而實現Drop則使智能指針具備自動析構的能力。安全

Deref

Deref有一個特性是強制隱式轉換:若是一個類型T實現了Deref<Target=U>,則該類型T的引用在應用的時候會被自動轉換爲類型U網絡

use std::rc::Rc;
fn main() {
    let x = Rc::new("hello");
    println!("{:?}", x.chars());
}

若是你查看Rc的源碼,會發現它並無實現chars()方法,但咱們上面這段代碼卻能夠直接調用,這是由於Rc實現了Deref。數據結構

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for Rc<T> {
    type Target = T;

    #[inline(always)]
    fn deref(&self) -> &T {
        &self.inner().value
    }
}

這就使得智能指針在使用時被自動解引用,像是不存在同樣。函數

Deref的內部實現是這樣的:學習

#[lang = "deref"]
#[doc(alias = "*")]
#[doc(alias = "&*")]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Deref {
    /// The resulting type after dereferencing.
    #[stable(feature = "rust1", since = "1.0.0")]
    type Target: ?Sized;

    /// Dereferences the value.
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    fn deref(&self) -> &Self::Target;
}

#[lang = "deref_mut"]
#[doc(alias = "*")]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait DerefMut: Deref {
    /// Mutably dereferences the value.
    #[stable(feature = "rust1", since = "1.0.0")]
    fn deref_mut(&mut self) -> &mut Self::Target;
}

DerefMut和Deref相似,只不過它是返回可變引用的。線程

Drop

Drop對於智能指針很是重要,它是在智能指針被丟棄時自動執行一些清理工做,這裏所說的清理工做並不只限於釋放堆內存,還包括一些釋放文件和網絡鏈接等工做。以前我老是把Drop理解成Java中的GC,隨着對它的深刻了解後,我發現它比GC要強大許多。設計

Drop的內部實現是這樣的:指針

#[lang = "drop"]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Drop {
    #[stable(feature = "rust1", since = "1.0.0")]
    fn drop(&mut self);
}

這裏只有一個drop方法,實現了Drop的結構體,在消亡以前,都會調用drop方法。

use std::ops::Drop;
#[derive(Debug)]
struct S(i32);

impl Drop for S {
    fn drop(&mut self) {
        println!("drop {}", self.0);
    }
}

fn main() {
    let x = S(1);
    println!("create x: {:?}", x);
    {
        let y = S(2);
        println!("create y: {:?}", y);
    }
}

上面代碼的執行結果爲

結果

能夠看到x和y在生命週期結束時都去執行了drop方法。

對智能指針的基本概念就先介紹到這裏,下面咱們進入正題,具體來看看每一個智能指針都有什麼特色吧。

Box

前面咱們已經提到了Box 在Rust中是用來在堆內存中保存數據使用的。它的使用方法很是簡單:

fn main() {
    let x = Box::new("hello");
    println!("{:?}", x.chars())
}

咱們能夠看一下Box::new的源碼

#[stable(feature = "rust1", since = "1.0.0")]
#[inline(always)]
pub fn new(x: T) -> Box<T> {
  box x
}

能夠看到這裏只有一個box關鍵字,這個關鍵字是用來進行堆內存分配的,它只能在Rust源碼內部使用。box關鍵字會調用Rust內部的exchange_malloc和box_free方法來管理內存。

#[cfg(not(test))]
#[lang = "exchange_malloc"]
#[inline]
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
    if size == 0 {
        align as *mut u8
    } else {
        let layout = Layout::from_size_align_unchecked(size, align);
        let ptr = alloc(layout);
        if !ptr.is_null() {
            ptr
        } else {
            handle_alloc_error(layout)
        }
    }
}

#[cfg_attr(not(test), lang = "box_free")]
#[inline]
pub(crate) unsafe fn box_free<T: ?Sized>(ptr: Unique<T>) {
    let ptr = ptr.as_ptr();
    let size = size_of_val(&*ptr);
    let align = min_align_of_val(&*ptr);
    // We do not allocate for Box<T> when T is ZST, so deallocation is also not necessary.
    if size != 0 {
        let layout = Layout::from_size_align_unchecked(size, align);
        dealloc(ptr as *mut u8, layout);
    }
}

Rc

在前面的學習中,咱們知道Rust中一個值在同一時間只能有一個變量擁有其全部權,但有時咱們可能會須要多個變量擁有全部權,例如在圖結構中,兩個圖可能對同一條邊擁有全部權。

對於這樣的狀況,Rust爲咱們提供了智能指針Rc (reference counting)來解決共享全部權的問題。每當咱們經過Rc共享一個全部權時,引用計數就會加一。當引用計數爲0時,該值纔會被析構。

Rc 是單線程引用計數指針,不是線程安全類型。

咱們仍是經過一個簡單的例子來看一下Rc 的應用吧。(示例來自 the book

若是咱們想要造一個「雙頭」的鏈表,以下圖所示,3和4都指向5。咱們先來嘗試使用Box實現。

雙頭鏈表

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5,
                 Box::new(Cons(10,
                               Box::new(Nil))));
    let b = Cons(3, Box::new(a));
    let c = Cons(4, Box::new(a));
}

上述代碼在編譯時就會報錯,由於a綁定給了b之後就沒法再綁定給c了。

Box沒法共享全部權

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
    println!("count a {}", Rc::strong_count(&a));
}

這時咱們能夠看到a的引用計數是3,這是由於這裏計算的是節點5的引用計數,而a自己也是對5的一次綁定。這種經過clone方法共享全部權的引用稱做強引用

Rust還爲咱們提供了另外一種智能指針Weak ,你能夠把它看成是Rc 的另外一個版本。它提供的引用屬於 弱引用。它共享的指針沒有全部權。但他能夠幫助咱們有效的避免循環引用。

RefCell

前文中咱們聊過變量的可變性和不可變性,主要是針對變量的。按照前面所講的,對於結構體來講,咱們也只能控制它的整個實例是否可變。實例的具體某個成員是否可變咱們是控制不了的。但在實際開發中,這樣的場景也是比較常見的。好比咱們有一個User結構體:

struct User {
    id: i32,
    name: str,
    age: u8,
}

一般狀況下,咱們只能修改一我的的名稱或者年齡,而不能修改用戶的id。若是咱們把User的實例設置成了可變狀態,那就不能保證別人不會去修改id。

爲了應對這種狀況,Rust爲咱們提供了Cell<T>RefCell<T>。它們本質上不屬於智能指針,而是能夠提供內部可變性的容器。內部可變性其實是一種設計模式,它的內部是經過一些unsafe代碼來實現的。

咱們先來看一下Cell<T>的使用方法吧。

use std::cell::Cell;
struct Foo {
    x: u32,
    y: Cell<u32>,
}

fn main() {
    let foo = Foo { x: 1, y: Cell::new(3)};
    assert_eq!(1, foo.x);
    assert_eq!(3, foo.y.get());
    foo.y.set(5);
    assert_eq!(5, foo.y.get());
}

咱們可使用Cell的set/get方法來設置/獲取起內部的值。這有點像咱們在Java實體類中的setter/getter方法。這裏有一點須要注意:Cell<T>中包裹的T必需要實現Copy纔可以使用get方法,若是沒有實現Copy,則須要使用Cell提供的get_mut方法來返回可變借用,而set方法在任何狀況下均可以使用。因而可知Cell並無違反借用規則。

對於沒有實現Copy的類型,使用Cell<T>仍是比較不方便的,還好Rust還提供了RefCell<T>。話很少說,咱們直接來看代碼。

use std::cell::RefCell;
fn main() {
    let x = RefCell::new(vec![1, 2, 3]);
    println!("{:?}", x.borrow());
    x.borrow_mut().push(5);
    println!("{:?}", x.borrow());
}

從上面這段代碼中咱們能夠觀察到RefCell<T>的borrow_mut和borrow方法對應了Cell<T>中的set和get方法。

RefCell<T>Cell<T>還有一點區別是:Cell<T>沒有運行時開銷(不過也不要用它包裹大的數據結構),而RefCell<T>是有運行時開銷的,這是由於使用RefCell<T>時須要維護一個借用檢查器,若是違反借用規則,則會引發線程恐慌。

總結

關於智能指針咱們就先介紹這麼多,如今咱們簡單總結一下。Rust的智能指針爲咱們提供了不少有用的功能,智能指針的一個特色就是實現了DropDeref這兩個trait。其中Droptrait中提供了drop方法,在析構時會去調用。Dereftrait提供了自動解引用的能力,讓咱們在使用智能指針的時候不須要再手動解引用了。

接着咱們分別介紹了幾種常見的智能指針。Box<T>能夠幫助咱們在堆內存中分配值,Rc<T>爲咱們提供了屢次借用的能力。RefCell<T>使內部可變性成爲現實。

最後再多說一點,其實咱們之前見到過的StringVec也屬於智能指針。

至於它們爲何屬於智能指針,Rust又提供了哪些其餘的智能指針呢?這裏就留個坑吧,感興趣的同窗能夠本身踩一下。

相關文章
相關標籤/搜索