七、智能指针

179 阅读6分钟

前言

阅读耗时2分钟

目录

七、智能指针.png

一、相关概念

  • 指针
    • 一个变量在内存中包含的是一个地址,也就是指向其它数据
    • Rust中最常见的指针就是"引用",使用 & ,只是借用数据
  • 智能指针
    • 行为和指针一样,但是有额外的元数据和功能,例如引用计数智能指针、StringVec<T>,都拥有一片内存区域,允许用户对其操作
    • 一般都是拥有它所指向的数据
    • Rust中存在不可变引用和可变引用(mut),同一时刻不能存在多个不可变引用,不能存在一个可变和一个不可变引用。智能指针的存在就是可以存在多个指针指向同一个引用,并且修改,即允许多重所有权场景。但是这种场景的使用一般只限于单线程。

二、智能指针实现

  • 智能指针通常使用了struct实现,并且实现了
    • DerefDrop这两个Trait
  • Deref Trait
    • 允许智能指针struct的实例像引用一样使用
  • Drop Trait
    • 允许你自定义当智能指针实例走出作用域时的代码

后面主要介绍一下几个智能指针类型及其使用

  • Box<T>
    • heap内存上分配
  • Rc<T>
    • 启用多重所有权的引用计数类型
  • Ref<T>RefMut<T>
    • 通过RefCell<T>访问,在运行时而不是编译时强制借用规则的类型

三、Box

应用场景

在编译时,某类型的大小无法确定,使用该类型时,上下文需要知道它的确切大小
当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制
使用某个值,你只关心它是否实现了特定trait,而不关心它的具体类型

使用

fn main() {
    let b = Box::new(5);
}//Box 指针和内存都会被释放

通过Box5存储在堆上了

enum List { 
    Cons(i32, List),
    Nil,
}

上述代码会报错,原因就是 recursive type List has infinite size,我们就可以通过Box修改一下,可以存放指针,指针大小是确定的。

use crate::List::{Cons, Nil};
fn main() {
    let b = Box::new(5);
    let list = Cons(1, 
        Box::new(Cons(2,
             Box::new(Cons(3, 
                Box::new(Nil))))));
}
enum List {
    Cons(i32, Box<List>),
    Nil,
}

综上,我们可以看出来
Box它没有性能开销,只提供了间接存储,和heap内存分配功能,它实现了Deref Trait (能够当做引用一样使用)和Drop Trait(内存在生命周期到达后自动释放)。

四、Deref Trait

这个可以理解为解引用,类似C++*,只有实现了Deref Trait,才能够像常规引用一样来使用。

fn main() {
    let x = 5;
    let y = &x;
    assert_eq!(x, *y);
}

上面代码等价于下面

fn main() {
    let x = 5;
    let y = Box::new(x);
    assert_eq!(x, *y);
}

接下来自定义个类似Box::new功能,实现Deref Trait

use std::ops::Deref;
fn main() {
    let x = 5;
    let y = MyBox::new(x);
    assert_eq!(x, *y);
}
struct MyBox<T>(T);
impl<T> MyBox<T> {
    fn new(x:T) -> MyBox<T>{
        MyBox(x)
    }
}
impl <T> Deref for MyBox<T> {
    type Target = T;//关联类型
    fn deref(&self) -> &T{
        &self.0
    }
}

两者结果一致,都相等,就是为MyBox实现了一个deref解引用方法。
并且解引用这个操作,编译器在编译时会隐式转换,例如

fn main() {
    let m = MyBox::new(String::from("Rust"));
    // m 是MyBox类型,编译器会制动将 &MyBox<String> deref 成 &String, String deref 后就是 &str
    hello(&m);
    hello("Rust");
}
fn hello(name: &str){
    println!("{}", name);
}

五、Drop Trait

实现了Drop Trait,这个智能指针离开作用域就能被释放内存。我们也可以自定义值将要离开作用域时发生的动作。

fn main() {
    let c = CustomSmartPointer{data: String::from("my stuff")};
    println!("CustomSmartPointers created");
}
struct CustomSmartPointer{
    data: String,
}
impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping with data {}", self.data);
    }
}
//输出:
//CustomSmartPointers created
//Dropping with data my stuff

我们也可以使用std::mem::drop函数,提前drop,并且不需要担心被drop多次。

六、Rc引用计数智能指针

应用场景
需要在heap上分配数据,这些数据被程序的多个部分读取但在编译时无法确定哪个部分最后使用完这些数据

  • Rc用法有
    • Rc::clone(&a),增加引用计数,只增加引用,不会执行数据的深度拷贝操作
    • Rc::strong_count(&a),获得引用计数
    • Rc::weak_count函数
fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after create a = {}", Rc::strong_count(&a));

    let b = Cons(3, Rc::clone(&a));
    println!("count after create b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after create c = {}", Rc::strong_count(&a));
    }

    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
enum List {
    Cons(i32, Rc<List>),
    Nil,
}
//输出
//count after create a = 1
//count after create b = 2
//count after create c = 3
//count after c goes out of scope = 2

Rc<T>这种引用是不可变的,只能读,不能写,这样就避免了多个不可变引用写数据资源产生竞争,不同步问题,只能用于单线程场景。

七、RefCell及内部可变性

之前说到Rc<T>不可变引用,对应肯定会有可变引用。
内部可变性是Rust的设计模式之一,它允许你在持有不可变引用的前提下对数据进行修改。

  • 具体主要是用unsafe代码来绕过Rust正常的可变性规则和借用规则。
    • 借用规则。在任何给定的时间内,只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
fn main() {
    let message = RefCell::new(vec![]);
    message.borrow_mut().push(String::from("hello1"));
    message.borrow().len();
}
  • RefCell<T>会记录当前存在多少个活跃的Ref<T>RefMut<T>智能指针
    • borrow方法,返回智能指针Ref<T>,实现了Deref,每调一次,不可变引用计数加1,离开作用域时被释放,不可变引用减1
    • borrow_mut方法,返回智能指针RefMut<T>,实现了Deref,每调一次,可变引用计数加1,离开作用域时,可变引用减1

八、循环引用内存泄漏

Rust的内存安全机制可以保证很难发生内存泄漏,但不是不可能
例如使用Rc<T>RefCell<T>就可能创造出循环引用,从而发生内存泄漏(每个项的引用数量不会变成0,值也不会被处理掉)

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
    println!("rc count = {}", Rc::strong_count(&a));
    println!("tail = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
    println!("a rc count after b = {}",Rc::strong_count(&a));
    println!("b init rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail(){
        *link.borrow_mut() = Rc::clone(&b); // a的第二元素个引用指向b
    }
    println!("b rc count after change = {}",Rc::strong_count(&b));
    println!("a rc count after change = {}", Rc::strong_count(&a));
}
    
#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}
impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>>{
        match  self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

结果

rc count = 1
tail = Some(RefCell { value: Nil })
a rc count after b = 2
b init rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after change = 2
a rc count after change = 2

上面已经造成了循环引用了。a的第二个元素持有b, b的第二个元素指向a
我们只需要在末尾加一行打印,就堆栈溢出了

println!("a next item = {:?}", a.tail());

防止内存泄漏的解决办法

  • 自己检查
  • Rc<T>强引用 改成 Weak<T> 弱引用形式。通过调用Rc::downgrade方法,可以创建弱引用,weak_count加1,Rc<T>计数要为0才会被释放内存,而Weak<T>则不是。
fn main(){
    let leaf = Rc::new(Node{
        value: 3,
        parent:RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });
   
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    let branch = Rc::new(Node{
        value: 5,
        parent:RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

}
#[derive(Debug)]
struct Node{
    value: i32,
    parent:RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

输出

leaf parent = None
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })