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又提供了哪些其余的智能指针呢?这里就留个坑吧,感兴趣的同窗能够本身踩一下。

相关文章
相关标签/搜索