rust 智能指针
1 相关概念
- 指针: 指向一块内存地址的变量,变量的值是内存地址
- 智能指针
- Rust 的智能指针是一类数据结构,它们对传统指针的功能进行了扩展
- 它们通常包含元数据(例如,引用计数)以管理内存和对象的生命周期
- Rust 标准库中定义了几种智能指针,如
Box<T>, Rc<T>, Arc<T>, Ref, RefMut, Cell, RefCell等 - 智能指针的主要目的是提供对内存安全、并发安全以及数据共享和所有权转移的更精细的控制
- 智能指针通常使用struct来实现,实现了Deref和Drop trait
- Deref trait 允许智能指针像引用一样使用, 例如解引用
- Drop trait 智能指针在离开作用域时执行的代码
2 Box
Box<T> 是一个智能指针,它允许在堆上存储数据,在栈上存放指向它的指针,并确保在离开作用域时正确清理堆上的数据
2.1 基本使用
我们运行一下这个,看看报错的信息
// 这里的定义本身就报错了, 因为这个 枚举类型的大小无法确定
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]是个表达式,是需要先计算它的大小的,需要在栈上计算和存储
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
因为Box 实现了Deref trait,所以可以对Box<T>进行解引用操作是可以的
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
- 相当于析构函数
- 通过实现 Drop trait, 可以自定义值离开作用域时发生的事情(比如文件资源的释放).
- 任何类型都可以实现这个 Drop trait
- 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> 引用计数智能指针
- Rc = reference counting
Rc<T>允许同一个数据有多个所有者- 当最后是0个引用,那么该值就会被清理
- 员工下班了,谁离开了是不能随便就熄灯关门,是等只有最后一个人走了,才能熄灯关门,这种需求的时候我们就可以用这个
Rc<T>类型 - 通过不可变引用来共享数据,只读
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>
- 单线程内部使用
- 数据所有者只有一个
- 运行时检查
报错了,因为你不能可变借用一个不可变的值
有这样的需求, 我们希望该值对外部是不可变的, 对于它内部可以有方法来修改它的值.
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
- 强引用
- Rc.clone 后rc的引用计数(strong_count)会+1, 实例只有在 strong_count=0,才会被释放
- 弱引用
- Rc::downgrade 会创建值的弱引用(weak_count+1),返回类型是 Weak
- weak_count不为0,不会影响值的释放
- strong_ount为0, 弱引用会自动断开
- 使用Weak
前,需要确保它指向的值依然存在
- Rc::downgrade 会创建值的弱引用(weak_count+1),返回类型是 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),
);
}