智能指针
智能指针概况
指针是一个包含内存地址的变量。这个地址指向一些其他的数据。
什么是智能指针
智能指针:
- 智能指针是一类数据结构,它们表现类似于指针,但是也拥有额外的元数据,最明显的,它们拥有一个引用计数。引用计数记录智能指针总共有多少个所有者,并且当没有任何所有者时清楚数据。
- 智能指针通常使用结构体实现。智能指针区别于常规结构体的显著特征在于其实现了
Deref
和Drop trait
。Deref trait
允许智能指针结构体实例表现的像引用一样,这样就可以编写即用于引用,又用于智能指针的代码Drop trait
允许我们自定义当智能指针离开作用域时执行的代码
普通引用和智能指针的一个额外区别是:引用只是借用数据的指针,而智能指针则是拥有它们指向的数据。
几个标准库中的智能指针
- Box是最简单的智能指针,只是用于Heap堆上分配
- Rc指针是为了完成share ownership的功能,是智能指针的核心,其数据可以有多个所有者
- Weak指针弱引用
- Arc是多线程的Rc
- Mutex是提供了可变性
- Rust提供了
Cell
和RefCell
用于内部可变性
注意📢:
尽量不要使用RefCell
。
Box
指针
Box
指针适用场景
box适用于以下场景:
- 当有一个在编译时未知大小的类型,而又需要再确切大小的上下文中使用这个类型值的时候(譬如,再一个
List
环境下,存放数据,但是每个元素的大小在编译时又不确定) - 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并关心它的类型是否实现了特定trait而不是其具体类型时
第一种场景下,假如我们现在有这样一个List
枚举,
enum List {
Cons(i32, List),
Nil,
}
当编译器编译的时候因为List
枚举存在递归,并不知道List
枚举应该分配多大的内存,这时候采用Box
的话,指针是有内存固定大小的。这样编译的话会通过,
use crate::List::{Cons, Nil};
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
trait Animal {
fn eat(&self);
}
#[derive(Debug)]
struct Cat {
children: Option<Box<Cat>>,
}
impl Animal for Cat {
fn eat(&self) {
println!("cat is eating");
}
}
fn main() {
let b = Box::new(5); // b存储在栈上,5存储在堆上,b指向5所在的内存
println!("b = {}", b);
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list); // Cons(1, Cons(2, Cons(3, Nil)))
let cat = Box::new(Cat{ children: None });
println!("{:?}", cat); // Cat { children: None }
let t: Box<dyn Animal>;
t = Box::new(Cat {
children: Some(cat),
});
t.eat(); // cat is eating
}
解引用
实现解引用和自定义MyBox
,
use std::ops::Deref;
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
}
}
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y); // 解引用
let z = Box::new(x);
assert_eq!(5, *z);
// 需要实现Deref特质
let a = MyBox::new(x);
assert_eq!(5, *a);
let m = MyBox::new(String::from("Rust"));
hello(&m); // 解引用的强制多态:将MyBox变为&String, 再将String解引用,变为&str,
println!("hello world");
}
fn hello(s: &str) {
println!("hello {s}");
}
解引用多态与可变性交互:
- 当
T: Deref<Target=U>
时,从&T
到&U
- 当
T: DerefMut<Target=U>
时,从&mut T
到&mut U
- 当
T: Deref<Target=U>
时,从&mut T
到&U
原始指针
- 声明需要
as
来标注类型以及可变或者不可变 - 解引用则需要
unsafe
fn main() {
// 指针不可变
let x: usize = 1;
let raw_ptr = &x as *const usize;
// 指针可变
let mut y: usize = 2;
let raw_mut_ptr = &mut y as *mut usize;
let some_usize = unsafe {
*raw_ptr
};
println!("some usize: {some_usize}"); // some usize: 1
let some_mut_usize = unsafe {
*raw_mut_ptr
};
println!("some mut usize: {some_mut_usize}"); // some mut usize: 2
}
Rc指针
Rc指针式存储ref count
的胖指针。
- Rc指针的追踪的两个方向
- 对单个值的多个引用
- 何时销毁该变量
Rc::clone
会创建一个新的reference,计数加一- 相对于Arc,Rc指针只能运行于单线程
- Rc相对于Weak,Rc是Strong
先看如下:
use crate::List::{Cons, Nil};
enum List {
Cons(i32, Box<List>),
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));
}
出现如下报错:
--> src/main.rs:9:30
|
7 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
8 | let b = Cons(3, Box::new(a));
| - value moved here
9 | let c = Cons(4, Box::new(a));
| ^ value used here after move
这时b
已经拿到a
的所有权,c
不能再使用a
了,这种情况下使用Rc指针把a
各克隆一份给b
和c
,写法如下
use crate::List::{Cons, Nil};
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
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));
let d = Cons(6, a.clone()); // 另一种写法
let e = Cons(7, a.clone());
}
现在我们给a
做引用计数,
use crate::List::{Cons, Nil};
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a: {}", Rc::strong_count(&a)); // count after creating a: 1
let b = Cons(3, Rc::clone(&a));
println!("count after binding to b, a count: {}", Rc::strong_count(&a)); // count after binding to b, a count: 2
{
let c = Cons(4, Rc::clone(&a));
println!("count after binding to c, a count: {}", Rc::strong_count(&a)); // count after binding to c, a count: 3
}
println!("count at end a: {}", Rc::strong_count(&a)); // count at end a: 2 由于c离开作用域
let d = Cons(6, a.clone()); // 另一种写法
let e = Cons(7, a.clone());
}
Cell
和RefCell
Rust提供了Cell
和RefCell
用于内部可变性,带来了灵活性,同样也带来了一些安全的隐患。
RefCell
:
- 内部可变性:允许在使用不可变引用时改变数据。
- 通过
RefCell<T>
在运行时检查借用规则(通常情况下,是在编译时检查借用规则),RefCell<T>
代表其数据的唯一所有权。 - 类似于
Rc<T>
,RefCell<T>
只能用于单线程场景。
选择Box<T>
、Rc<T>
或RefCell<T>
的理由
Rc<T>
允许相同数据有多个所有者(数据只读共享),Box<T>
和RefCell<T>
有单一所有者。Box<T>
允许在编译时执行不可变或可变借用检查;Rc<T>
仅允许在编译时执行不可变借用检查;RefCell<T>
允许在运行时执行不可变或可变借用检查。因为
RefCell<T>
允许在运行时执行可变借用检查,所以我们可以在即使RefCell<T>
自身是不可变的情况下修改其内部的值。
Cell
和RefCell
的区别
Cell
只适用于Copy
类型,用于提供值,而RefCell
用于提供引用Cell
不会panic
,而RefCell
会
use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::{Cell, RefCell};
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
fn main() {
let val = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&val), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(7)), Rc::clone(&a));
println!("a before {:?}", a); // a before Cons(RefCell { value: 5 }, Nil)
println!("b before {:?}", b); // b before Cons(RefCell { value: 6 }, Cons(RefCell { value: 5 }, Nil))
println!("c before {:?}", c); // c before Cons(RefCell { value: 7 }, Cons(RefCell { value: 5 }, Nil))
*val.borrow_mut() += 10; // 修改内部的RefCell,Rc是只读的不可变引用,只提供数据共享
println!("a after {:?}", a); // a after Cons(RefCell { value: 15 }, Nil)
println!("b after {:?}", b); // b after Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
println!("c after {:?}", c); // c after Cons(RefCell { value: 7 }, Cons(RefCell { value: 15 }, Nil))
// &str是copy, String不copy
let c = Cell::new("yzzy");
let c1 = c.get();
println!("{c1}"); // yzzy
c.set("原子");
let c2 = c.get();
println!("{c1}"); // yzzy
println!("{c2}"); // 原子
}
循环引用和自引用
循环引用是什么
Rust 的安全性是众所周知的,但是不代表它不会内存泄漏。一个典型的例子就是同时使用 Rc<T>
和 RefCell<T>
创建循环引用,最终这些引用的计数都无法被归零,因此 Rc<T>
拥有的值也不会被释放清理。
use std::rc::Rc;
use std::cell::RefCell;
use crate::List::{Cons, Nil};
#[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,
}
}
}
这里我们创建一个有些复杂的枚举类型 List
,这个类型很有意思,它的每个值都指向了另一个 List
,此外,得益于 Rc 的使用还允许多个值指向一个 List
:
如上图所示,每个矩形框节点都是一个 List
类型,它们或者是拥有值且指向另一个 List
的Cons
,或者是一个没有值的终结点 Nil
。同时,由于 RefCell
的使用,每个 List
所指向的 List
还能够被修改。
下面来使用一下这个复杂的 List
枚举:
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("1 a rc count: {}", Rc::strong_count(&a)); // 1 a rc count: 1
println!("1 a tail: {:?}", a.tail()); // 1 a tail: Some(RefCell { value: Nil })
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("2 a rc count: {}", Rc::strong_count(&a)); // 2 a rc count: 2
println!("2 b rc count: {}", Rc::strong_count(&b)); // 2 b rc count: 1
println!("2 a tail: {:?}", b.tail()); // 2 a tail: Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("3 a rc count: {}", Rc::strong_count(&a)); // 3 a rc count: 2
println!("3 b rc count: {}", Rc::strong_count(&b)); // 3 b rc count: 2
}
a
和b
的关系变的如下图所示:
但是假如我们添加如下作用域,
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("1 a rc count: {}", Rc::strong_count(&a)); // 1 a rc count: 1
println!("1 a tail: {:?}", a.tail()); // 1 a tail: Some(RefCell { value: Nil })
{
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("2 a rc count: {}", Rc::strong_count(&a)); // 2 a rc count: 2
println!("2 b rc count: {}", Rc::strong_count(&b)); // 2 b rc count: 1
println!("2 a tail: {:?}", b.tail()); // 2 a tail: Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("3 a rc count: {}", Rc::strong_count(&a)); // 3 a rc count: 2
println!("3 b rc count: {}", Rc::strong_count(&b)); // 3 b rc count: 2
}
// ... 关于a的代码
}
b
在离开作用域以后被销毁,而a
引用b
,因此b
的引用计数不会变成0,而是变成1。但是这种情况会出现两个问题:
- 造成
b
的内存泄露,因为在b
离开作用域以外的代码,b
被分配在Rc的堆上内存不会被丢弃 - 打印
a.tail()
会出现死循环
创建循环引用并不简单,但是也并不是完全遇不到,当你使用 RefCell<Rc<T>>
或者类似的类型嵌套组合(具备内部可变性和引用计数)时,就要打起万分精神,前面可能是深渊!
那么问题来了? 如果我们确实需要实现上面的功能,该怎么办?答案是使用 Weak
。
弱引用
弱引用Weak<T>
:
- 弱引用通过
Rc::downgrade
传递Rc实例的引用,调用Rc::downgrade
会得到Weak<T>
类型的智能指针,同时将weak_count
加1(不是strong_count
加1) - 区别在于
weak_count
无需计数为0就能使Rc实例被清理。只要strong_count
为0就可以了 - 可以通过
Rc::upgrade
方法返回Option<Rc<T>>
对象
Weak 非常类似于 Rc,但是与 Rc 持有所有权不同,Weak 不持有所有权,它仅仅保存一份指向数据的弱引用:如果你想要访问数据,需要通过 Weak 指针的 upgrade
方法实现,该方法返回一个类型为 Option<Rc<T>>
的值。
看到这个返回,相信大家就懂了:何为弱引用?就是不保证引用关系依然存在,如果不存在,就返回一个 None
!
因为 Weak 引用不计入所有权,因此它无法阻止所引用的内存值被释放掉,而且 Weak 本身不对值的存在性做任何担保,引用的值还存在就返回 Some
,不存在就返回 None
。
Weak和Rc对比
我们来将 Weak 与 Rc 进行以下简单对比:
Weak | Rc |
---|---|
不计数 | 引用计数 |
不拥有所有权 | 拥有值的所有权 |
不阻止值被释放 | 所有权计数归零,才能drop |
引用的值存在返回Some ,不存在返回None | 引用的值必定存在 |
通过upgrade 取到Option<Rc<T>> ,然后再取值 | 通过Deref 自动解引用,取值无需任何操作 |
通过这个对比,可以非常清晰的看出 Weak 为何这么弱,而这种弱恰恰非常适合我们实现以下的场景:
- 持有一个 Rc 对象的临时引用,并且不在乎引用的值是否依然存在
- 阻止 Rc 导致的循环引用,因为 Rc 的所有权机制,会导致多个 Rc 都无法计数归零
使用方式简单总结下:对于父子引用关系,可以让父节点通过 Rc 来引用子节点,然后让子节点通过 Weak 来引用父节点。
Weak 总结
因为 Weak 本身并不是很好理解,因此我们再来帮大家梳理总结下,然后再通过一个例子,来彻底掌握。
Weak 通过 use std::rc::Weak
来引入,它具有以下特点:
- 可访问,但没有所有权,不增加引用计数,因此不会影响被引用值的释放回收
- 可由
Rc<T>
调用downgrade
方法转换成Weak<T>
Weak<T>
可使用upgrade
方法转换成Option<Rc<T>>
,如果资源已经被释放,则Option
的值是None
- 常用于解决循环引用的问题
我们还是举List
的例子:
use std::{cell::RefCell, rc::{Rc, Weak}};
use crate::List::{Cons, Nil};
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Weak<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Weak<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
RefCell
中的List
变为Weak
,
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Weak::new())));
println!("1, a strong count: {}, weak count: {}", Rc::strong_count(&a), Rc::weak_count(&a)); // 1, a strong count: 1, weak count: 0
println!("1, a tail: {:?}", a.tail()); // 1, a tail: Some(RefCell { value: (Weak) })
let b = Rc::new(Cons(10, RefCell::new(Weak::new())));
if let Some(link) = b.tail() {
*link.borrow_mut() = Rc::downgrade(&a);
}
println!("2, a strong count: {}, weak count: {}", Rc::strong_count(&a), Rc::weak_count(&a)); // 2, a strong count: 1, weak count: 1
println!("2, b strong count: {}, weak count: {}", Rc::strong_count(&b), Rc::weak_count(&b)); // 2, b strong count: 1, weak count: 0
println!("2, b tail: {:?}", b.tail()); // 2, b tail: Some(RefCell { value: (Weak) })
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::downgrade(&b);
}
println!("3, a strong count: {}, weak count: {}", Rc::strong_count(&a), Rc::weak_count(&a)); // 3, a strong count: 1, weak count: 1
println!("3, b strong count: {}, weak count: {}", Rc::strong_count(&b), Rc::weak_count(&b)); // 3, b strong count: 1, weak count: 1
println!("3, a tail: {:?}", a.tail()); // 3, a tail: Some(RefCell { value: (Weak) })
}
一开始,a
的weak_count
为0,因为没有被引用,当b
的尾端指向a
以后,a
的weak_count
变为1。同理,当a
的尾端指向b
后,b
的weak_count
变为1。我们这时再打印a.tail()
,发现并不会出现死循环,因为它不在乎引用的值是否依然存在。
Tree数据结构
我们先定义Node
的结构体,
use std::{cell::RefCell, rc::{Rc, Weak}};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
child: RefCell<Vec<Rc<Node>>>,
}
然后分别创建leaf
子节点和branch
作为父节点,
fn main() {
let leaf = Rc::new(Node{
value: 3,
parent: RefCell::new(Weak::new()),
child: RefCell::new(vec![]),
});
println!("1 leaf strong: {}, weak: {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf)); // 1 leaf strong: 1, weak: 0
println!("leaf parent: {:?}", leaf.parent.borrow().upgrade()); // leaf parent: None
let branch = Rc::new( Node {
value: 5,
parent: RefCell::new(Weak::new()),
child: RefCell::new(vec![Rc::clone(&leaf)]), // branch作为父节点拥有leaf作为子节点
});
println!("1 branch strong: {}, weak: {}", Rc::strong_count(&branch), Rc::weak_count(&branch)); // 1 branch strong: 1, weak: 0
println!("2 leaf strong: {}, weak: {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf)); // 2 leaf strong: 2, weak: 0
*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // leaf节点的parent指向branch父节点
println!("2 branch strong: {}, weak: {}", Rc::strong_count(&branch), Rc::weak_count(&branch)); // 2 branch strong: 1, weak: 1
println!("leaf parent: {:#?}", leaf.parent.borrow().upgrade());
// leaf parent: Some(
// Node {
// value: 5,
// parent: RefCell {
// value: (Weak),
// },
// child: RefCell {
// value: [
// Node {
// value: 3,
// parent: RefCell {
// value: (Weak),
// },
// child: RefCell {
// value: [],
// },
// },
// ],
// },
// },
// )
}
Arc指针
- Rc并不支持
Send trait
和Sync trait
,会产生数据竞争 - Arc实现了原子操作,是线程安全的
- Arc是有性能损耗的
Arc来自于std::sync::Arc
,同样也有sync
版本的Weak。
use std::{sync::{Arc, Weak}, thread};
#[derive(Debug)]
struct Owner {
name: String,
dogs: Vec<Weak<Dog>>,
}
#[derive(Debug)]
struct Dog {
owner: Arc<Owner>,
}
fn main() {
let someone = Arc::new(Owner {
name: "tom".to_string(),
dogs: vec![],
});
for i in 0..10 {
let someone = Arc::clone(&someone);
let join_handle = thread::spawn(move || {
let yellow_dog = Arc::new(Dog {
owner: Arc::clone(&someone),
});
let black_dog = Arc::new(Dog {
owner: Arc::clone(&someone),
});
println!("yellow dog owner: {}", yellow_dog.owner.name);
println!("black dog owner: {}", black_dog.owner.name);
println!("thread {i} end");
});
_ = join_handle.join();
}
}