十三、智能指针
1. 基本概念
- 指针:包含内存地址的变量
- Rust 中,最常见的指针是 引用 ,除了引用数据外,没有其他任何特殊功能,也没有额外开销
- 跟 C 语言的指针概念是类似的
- 智能指针:一类数据结构,表现类似于指针,但拥有额外的元数据和功能
- 起源于 C++,在其他语言中也存在
- 通常使用结构体实现
- 与普通指针的区别:普通指针是一类只 借用 数据的指针;大部分情况下,智能指针会 拥有 其指向的数据
- 与一般结构体的区别:智能指针实现了
Deref和Drop两个 traitDeref:允许智能指针结构体实例表现得像引用一样Drop:允许自定义运行于智能指针离开作用域后的代码
2. Box<T>
- 特性
- 指向堆上的数据,允许你将一个值放到堆上而不是栈上,栈中只保留指向堆数据的指针
- 没有性能损失,也没有多少额外功能
- 常用场景
- 当有一个在编译时未知大小的类型,而又想要在需要知道确切大小的上下文中使用这个类型值时
- 当有大量数据,并希望在确保数据不被拷贝的情况下转移所有权时
- 当希望拥有一个值,并只关心它的类型是否实现了特定 trait ,而不关心其具体类型时
- 例如:创建递归类型
- 注意:Rust 在编译时,就需要知道某个类型要占用多少空间
Box<T>实现了Deref和Drop两个 trait
3. Deref trait
-
来源:
std::ops::Deref -
deref: 解引用
-
一个
struct实现这个 trait 后,这个struct类型的引用就可以重载 解引用运算符 了,对应的引用就可以使用解引用运算符*了 -
DerefMuttrait 作用与Deref作用相似,唯一不同的是DerefMut用于重载可变引用的*运算符 -
使用示例
// 来自官方教程的一个示例 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; fn deref(&self) -> &T { &self.0 // 返回当前值的引用 } } fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y); } // rust 底层运行 *y 时,实际上是运行了: *(y.deref()) // 如果 deref 方法直接返回值而不是值的引用,就会导致这个值的所有权被移出 self,从而导致问题 -
解引用强制多态
- 将实现了
Deref的类型的变量的引用,转换为可通过Deref转换的 原始类型 类型的变量的引用 - 简言之,如果一个引用类型是 可解引用的 ,则通过解引用运算符
*对其解引用时,会对这个引用值递归执行解引用,直到不能继续解引用为止 - 强制进行解引用强制多态
- 当
T: Deref<Target=U>时从&T到&U - 当
T: DerefMut<Target=U>时从&mut T到&mut U - 当
T: Deref<Target=U>时从&mut T到&U- Rust 也会将可变引用强转为不可变引用
- 简单地说:如果你需要对某个变量 a 进行解引用,如果 a 解引用的值还可解引用,则会继续对 a 进行解引用得到它的值 b,直到获取到的值是一个不可解引用的值为止,像一个递归的过程一样
- 当
- 将实现了
4. Drop trait
-
作用:当值要离开作用域时,允许执行一些额外的代码
-
特性
- 可以为任何类型提供
Droptrait 的实现,这段代码也将被用于释放各类资源,如文件、网络连接等 - 当实例离开作用域 Rust 会自动调用
drop方法,变量以被创建时相反的顺序被丢弃 drop方法不允许被主动调用,如果要提前强制释放变量,可以使用std::mem::drop
- 可以为任何类型提供
-
实现
-
要求实现一个
drop方法,这个方法只有一个入参:&mut self// 官方教程的一个示例 struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("my stuff") }; let d = CustomSmartPointer { data: String::from("other stuff") }; println!("CustomSmartPointers created."); } // 变量 d 比变量 c 先被销毁 // 原因:这些变量的引用是保存在栈中,栈是后进先出 // 续:显示清理变量 use std::mem::drop; fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }
-
5. Rc<T>
-
Rc (reference counting) 即 引用计数
-
作用:启用多所有权。当我们希望在堆上分配一些内存,这些内存供程序的多个部分读取,且无法在编译时确定程序的哪一部分会最后结束使用它时,就可以使用引用计数。
-
注意:这个智能指针 只能 用于单线程场景
-
注意:
Rc<T>只允许在程序的多个部分之间只读地共享数据,不然会导致数据竞争和不一致 -
使用示例
// 来自官方教程的一个示例 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)); let b = Cons(3, Rc::clone(&a)); // 相当于 `浅拷贝`,只会增加引用计数,不会真实拷贝,无性能问题 println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); }
6. RefCell<T>
-
内部可变性:Rust 中的一个特定设计,允许 developer 即使在有不可变引用时也可以改变数据
- 含义:在不可变值的内部改变值
- 类似于
ECMAScript 6中用const定义的对象,虽然不可以直接为这个变量重新赋值,但可以为这个对象的属性重新赋值
-
RefCell<T>具有数据的唯一所有权 -
与其他引用的借用规则的区别
- 对于引用和
Box<T>,它们的借用规则的不可变性作用于编译时- 违反规则时,会导致编译错误
- 对于
RefCell<T>,它的借用规则的不可变性作用于运行时- 违反规则时,会导致 panic
- 对于引用和
-
注意:这个智能指针 只能 用于单线程场景
-
注意:
RefCell在任何时候只允许有多个不可变借用或一个可变借用- 如果违反这个规则,就会在运行时
panic!
- 如果违反这个规则,就会在运行时
-
智能指针的对比
BoxRcRefCell所有者 单一 多个 单一 编译时借用检查 不可变或可变 不可变 不可变 运行时借用检查 - - 不可变或可变 -
借用规则的一个推论:对于一个不可变值,不能可变地借用这个值
fn main() { let b = Box::new(55); let mut b1 = &mut b; // 这是不允许的,会报错 let mut c = Box::new(55); let mut c1 = &mut c; // 这是允许的,因为 c 是可变的 } -
RefCell<T>的常用方法borrow: 获取不可变引用- 返回
Ref类型的智能指针
- 返回
borrow_mut: 获取可变引用- 返回
RefMut类型的智能指针
- 返回
7. 引用循环与内存泄露
-
Rust 并不能保证完全避免内存泄露
-
创建了循环引用会导致一些内存不会被释放,从而导致内存泄露
-
循环引用的示例
// 来自官方教程的一个示例 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, } } } use crate::List::{Cons, Nil}; use std::rc::Rc; use std::cell::RefCell; #[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, } } } fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a initial rc count = {}", Rc::strong_count(&a)); 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)); println!("b initial rc count = {}", Rc::strong_count(&b)); println!("b next item = {:?}", b.tail()); // 让 a 的尾部指向 b,从而产生循环引用 if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); } println!("b rc count after changing a = {}", Rc::strong_count(&b)); println!("a rc count after changing a = {}", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack // println!("a next item = {:?}", a.tail()); } -
避免循环引用
- 将
Rc<T>变为Weak<T> Weak<T>: 弱引用计数的智能指针weak_count: 不为 0 时也可被清理- 创建弱引用
Weak::new()Rc::downgrade(&target)
- 将
-
关于强引用、弱引用的一个示例
// 改编自官方教程的一个示例 use std::cell::RefCell; use std::rc::{Rc, Weak}; #[derive(Debug)] pub struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn show_counting(node: &Rc<Node>) { println!( "node's counting is: strong_count = {}, weak_count = {}", Rc::strong_count(&node), Rc::weak_count(&node), ); } fn show_node(node: &Rc<Node>) { println!("node value = {}", node.value); if node.children.borrow().len() == 0 { println!("node doesn't have any children, it's a leaf"); } // upgrade: 把弱引用升级为强引用。如果升级成功就会返回对应的 Rc,如果升级失败就返回 None match node.parent.borrow().upgrade() { None => println!("node doesn't have parent."), Some(parent_node) => println!("node's parent is {:?}", parent_node), } println!("--- --- ---") } pub fn create_tree() { // 叶节点,子节点为空数组 let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); show_counting(&leaf); show_node(&leaf); // 枝节点,有一个子节点 let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); // downgrade: 把强引用降级为弱引用 *leaf.parent.borrow_mut() = Rc::downgrade(&branch); show_counting(&leaf); show_node(&leaf); show_counting(&branch); }