rust 智能指针

Created

2024-05-11 14:54:38

Updated

2024-10-21 15:44:46

1 相关概念

  1. 指针: 指向一块内存地址的变量,变量的值是内存地址
  2. 智能指针
    1. Rust 的智能指针是一类数据结构,它们对传统指针的功能进行了扩展
    2. 它们通常包含元数据(例如,引用计数)以管理内存和对象的生命周期
    3. Rust 标准库中定义了几种智能指针,如 Box<T>, Rc<T>, Arc<T>, Ref, RefMut, Cell, RefCell
    4. 智能指针的主要目的是提供对内存安全、并发安全以及数据共享和所有权转移的更精细的控制
  • 智能指针通常使用struct来实现,实现了Deref和Drop trait
    • Deref trait 允许智能指针像引用一样使用, 例如解引用
    • Drop trait 智能指针在离开作用域时执行的代码

2 Box

Box<T> 是一个智能指针,它允许在堆上存储数据,在栈上存放指向它的指针,并确保在离开作用域时正确清理堆上的数据

2.1 基本使用

fn main() {
    // 5 是在 堆上分配的, 栈上的变量b 指向了 它
    let b = Box::new(5);
    println!("b = {}", b);
}

我们运行一下这个,看看报错的信息

// 这里的定义本身就报错了, 因为这个 枚举类型的大小无法确定
enum List {
    Cons(i32, List),
    Nil,
}

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

fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

提示使用Box ,这样 cons(i32, Box<List>) 大小就确定了.

help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
  |
2 |     Cons(i32, Box<List>),

修改后

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

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

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
Caution
  • 执行下面代码我们可以推测出, Box::new([1i32; 16])会先在栈上创建数组[1i32; 16],然后再将它复制到堆上.
  • 原因是数组 [1;16] 是个表达式,是需要先计算它的大小的,需要在栈上计算和存储
fn main() {
    let a = 11;
    let b = Box::new([1i32; 16]);
    let c = 5;
    println!("{:p}", &a);
    println!("{:p}", &b);
    println!("{:p}", &c);
}

2.2 获取裸指针

获取Box的裸指针
fn main() {
    let f;
    {
        let b = Box::new(5);
        println!("{:p}", b); // 堆上的5的地址
        println!("{:p}", &b); // 栈上b变量的地址
        // 将 b 转换为 *mut T 可变裸指针
        // c 的类型是 这里是 *mut i32
        // b 被move 掉了,但是这个c指向了 b指向的内存,且c没有这块内存的所有权
        let c = Box::into_raw(b);
        println!("{:p}", c); // 堆上5的地址
        unsafe {
            println!("{}", *c);
            *c = 23;
            f = &*c;
            // Box::from_raw 重新获取所有权,这样离开作用域后, 堆上的数据5被回收了
            let s = Box::from_raw(c);
        }
    }
    println!("f=={}", *f);  // 指向的数据已经不确定了,是不对的
}
fn main() {
    let d;
    {
        let mut b = Box::new(5);
        println!("{:p}", b); // 堆上的5的地址
        // 第二种方式来获取 裸指针
        // unsafe 那里对 *d 做修改, 就是需要对d 所指向的数据修改
        // 那么就是 对 *b 做修改, 则需要 对 *b 做 &mut 处理
        d = &mut *b as *const i32 as *mut i32;
        unsafe {
            println!("{}", *d);
        }
    } // b drop 了, 指向的堆内存 释放了

    println!("{:p}", d);
    // println!("{}", b);
    unsafe {
        // 数据不对了
        println!("{}", *d);
    }
}

3 deref trait

3.1 Box

fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);// *y 解引用 得到 x
}

因为Box 实现了Deref trait,所以可以对Box<T>进行解引用操作是可以的

fn main() {
    let x = 5;  // 栈上的5
    let y = Box::new(x); // 将5复制到堆上

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

3.2 隐式解引用转化(Deref coercion)

Tip

当把某个类型T(实现了Deref trait)的引用传递给一个函数或方法时,与函数或方法定义的参数类型不一样时,会自动进行式解引用转化,转化为经过 deref 操作后返回的那个引用

可变引用的情况需要实现 DerefMut trait

  • 下面三种情况会发生自动转化
    • From &T to &U when T: Deref<Target=U>
    • From &mut T to &mut U when T: DerefMut<Target=U>
    • From &mut T to &U when T: Deref<Target=U>
struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;
    // 实现 Deref trait 的这个deref 方法的返回值,就是我们 * 操作的真正目标
    // 因为返回的是引用, 所以 * 操作 ok的
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    // &m: MyBox<String> deref 转化为 &String
    // String 也实现了 Deref trait ,&String deref返回的是 &str
    // 这些过程在编译时就完成了, 所以不会有额外的开销
    hello(&m);
    // 等价于
    hello(&(*m)[..])
}

3.3 自定义智能指针

struct MyBox<T>(T, T);

impl<T> MyBox<T> {
    fn new(x: T, y: T) -> MyBox<T> {
        MyBox(x, y)
    }
}
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;
    // 实现 Deref trait 的这个deref 方法的返回值,就是我们 * 操作的真正目标
    // 因为返回的是引用, 所以 * 操作 ok的
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x, 12);
    // 如果没有实现 Deref trait, 那么 * 操作就会报错
    println!("{}", *y); // 12
    // 显然 上面实现的 deref(&self)方法是 类型MyBox的方法,
    // 所以可以 y.deref() 获取
    println!("{}", *(y.deref())); // 12
}

4 drop trait

  1. 相当于析构函数
  2. 通过实现 Drop trait, 可以自定义值离开作用域时发生的事情(比如文件资源的释放).
  3. 任何类型都可以实现这个 Drop trait
    1. drop 一个 实现了copy的类型, 不会执行任何操作
struct CustomSmartPointer {
    data: String,
}
// Drop 在预导入模块里, 所以不需要use
impl Drop for CustomSmartPointer {
    // 参数是可变借用 &mut self
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let a = CustomSmartPointer {
        data: String::from("hello stuff"),
    };
    // 无法显示的调用 drop 方法
    // a.drop()
    // 可以使用  std::mem::drop(a); 来提前调用
    // drop(a);  这样就行了, 因为drop 是预导入模块里的
    {
        let c = CustomSmartPointer {
            data: String::from("my stuff"),
        };
    }// c 最先执行drop, 所以先打印 my stuff
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

5 Rc<T> 引用计数智能指针

  1. Rc = reference counting
  2. Rc<T> 允许同一个数据有多个所有者
  3. 当最后是0个引用,那么该值就会被清理
  4. 员工下班了,谁离开了是不能随便就熄灯关门,是等只有最后一个人走了,才能熄灯关门,这种需求的时候我们就可以用这个 Rc<T> 类型
  5. 通过不可变引用来共享数据,只读
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 被move了
}

使用 Rc<T> 就可以解决这个问题

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)))));
    println!("count after creating a = {}", Rc::strong_count(&a)); //1
    // Rc::clone() 只会使 引用计数+1, 不会进行深度 copy
    // a.clone()  会进行深度copy
    let b = Cons(3, Rc::clone(&a)); // a 的引用计数+1
    println!("count after creating b = {}", Rc::strong_count(&a)); //2
     {
        let d = Cons(4, Rc::clone(&a));
        println!("count after creating d = {}", Rc::strong_count(&a)); // 3
    } // 这里d离开作用域,drop操作, 所以a的引用计数-1, 变为2
    let c = Cons(4, Rc::clone(&a)); // a 的引用计数+1
    println!("count after creating c = {}", Rc::strong_count(&a)); // 3
}

6 RefCell<T>

  • 单线程内部使用
  • 数据所有者只有一个
  • 运行时检查

报错了,因为你不能可变借用一个不可变的值

fn main() {
    let x = 5;
    let y = &mut x;
}

有这样的需求, 我们希望该值对外部是不可变的, 对于它内部可以有方法来修改它的值.

RefCell 就是能够可变借用一个原本不可变的值

use std::cell::RefCell;

fn main() {
    let shared_map = RefCell::new(vec![1, 2, 3]);
    // 创建1个可变引用
    let mut first_ref = shared_map.borrow_mut();
    // 不允许多个可变借用同时存在
    // let mut second_ref = shared_map.borrow_mut();

    // 修改通过不可变引用获取的数据
    first_ref.push(4);
    println!("{:?}", shared_map);
}
use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    // 多个不可变借用
    let borrowed_five = c.borrow();
    let borrowed_five2 = c.borrow();
}
  • 每次 调用 .borrow() 方法, 不可变借用计数+1, 其返回值离开作用域,不可变计数-1
  • 每次调用 .borrow_mut() 方法, 可变借用计数+1,其返回值离开作用域,可变计数-1
  • 根据上面2点来检查借用规则, 只允许一个可变借用,或多个不可变借用

7 Rc RefCell 结合使用

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

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

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

8 引用循环

8.1 Rc

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

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        // self: &Self
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a)); // 1
    println!("a next item = {:?}", a.tail());
    // b的下一个元素是a
    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a)); //2
    println!("b initial rc count = {}", Rc::strong_count(&b)); //1
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        // a的下一个元素是b, a和b 之间循环引用了.
        *link.borrow_mut() = Rc::clone(&b); // b +1
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b)); //2
    println!("a rc count after changing a = {}", Rc::strong_count(&a)); //2

    // 栈溢出了, 因为打印 a.tail() ,就是要打印b, 而要完全打印b,就需要打印a的值,因为b的"下一个元素"是a
    // println!("a next item = {:?}", a.tail());
} // 离开作用域, b 做引用计数-1操作,那么还有1个, 接着a离开作用域 引用计数-1,也是还剩1个. 这样2个都没被释放

8.2 Weak

  1. 强引用
    1. Rc.clone 后rc的引用计数(strong_count)会+1, 实例只有在 strong_count=0,才会被释放
  2. 弱引用
    1. Rc::downgrade 会创建值的弱引用(weak_count+1),返回类型是 Weak
    2. weak_count不为0,不会影响值的释放
    3. strong_ount为0, 弱引用会自动断开
    4. 使用Weak前,需要确保它指向的值依然存在
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
    // upgrade: 从 Weak<T> 获取一个 Rc<T>
    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!(
        "branch strong = {}, weak = {}",
        Rc::strong_count(&branch),
        Rc::weak_count(&branch),
    );

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf), //2
        Rc::weak_count(&leaf),
    );

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}
Back to top