0 简介

  • 引用:最常见的指针类型,不拥有资源的所有权
  • 智能指针:一类表现类似指针的数据结构
    • 通常拥有资源的所有权
    • 通常实现了DerefDrop特性
    • 常用的智能指针
      • Box<T> 用于在堆上分配值
      • Rc<T> 一个引用计数类型,其数据可以有多个所有者
      • Ref<T> RefMut<T>RefCell<T>

1 Box<T>

Box<T>的用途如下:

  • 在需要确定大小的上下文中使用大小未知类型(如递归类型),本节讨论这种情况
  • 需要在不复制数据的情况下转移大量数据的所有权时。转移栈上数据的所有权将导致数据复制,对于大量数据 ,复制时间可能很长,这时候可以使用Box<T>:转移Box<T>的所有权只需要复制栈上的数据指针,而不需要复制堆上的数据,所以开销很小。
  • 需要一个实现了特定trait的不确定类型时,这就是第17章将讨论的特性对象(trait object)

示例1:不使用Box<T>创建递归类型

  • 下述代码无法通过编译,提示递归类型List具有无限大小,建议使用boxRc或者&实现的间接引用
  • 非递归的枚举类型的大小,等于最大字段的大小
  1. enum List{
  2. Cons(i32,List),
  3. Nil,
  4. }
  5. fn main(){
  6. let list = Cons(1,Cons(2,Cons(3,Nil)));
  7. }

示例2:使用Box<T>创建递归类型

  • Box<T>提供了间接存储和堆分配,没有其他特殊功能,所以没有其他智能指针的额外开销
  • Box<T>实现了Deref特性,所以是智能指针,可以被当做引用对待。
  • 离开作用域时,Box<T>实现的Drop特性中的drop方法被调用,这个方法会清理堆上的数据(栈上的指针被自动清理)。
  1. enum List{
  2. Cons(i32,Box<List>),
  3. Nil,
  4. }
  5. fn main(){
  6. let list = Cons(1,Box::new(Cons(2,Box::new(Cons(3,Box::new(Nil))))));
  7. }

2 Deref特性

  • 引用可看做是指针,可通过解引用运算符 * (星号)访问指针指向的值
  • Box<T> 实现了Deref特性,可以被当做指针使用,即也可以对Box<T>类型使用解引用运算符
  1. let x = 5;
  2. let y = &x;
  3. let z = Box::new(x);
  4. assert_eq!(x,5);
  5. assert_eq!(*y,5);
  6. assert_eq!(*z,5);// 对Box<T>类型使用解引用运算符

2.1 实现Deref特性

  • 自定义类型MyBox<T>实现std::ops::Deref特性后,就可以对其实例使用解引用运算符了
  • 对实现了std::ops::Deref的类型实例使用解引用运算符时,实际上相当于*(z.deref()),所以deref()方法必须返回引用值:如果返回的不是引用值,则不能对其使用解引用运算符
  1. struct MyBox<T>(T);
  2. impl<T> MyBox<T>{
  3. fn new(x: T) -> MyBox<T>{
  4. MyBox(x)
  5. }
  6. }
  7. use std::ops::Deref;
  8. impl<T> Deref for MyBox<T>{
  9. type Target = T;
  10. fn deref(&self) -> &T{
  11. &self.0
  12. }
  13. }
  14. fn main(){
  15. let x = 5;
  16. let y = &x;
  17. let z = MyBox::new(x);
  18. assert_eq!(x,5);
  19. assert_eq!(*y,5);
  20. assert_eq!(*z,5);
  21. }

2.2 解引用强制多态(deref coercions)

  • 传递函数/方法参数时,Rust会在必要时多次调用Deref特性的deref方法,使得传入的参数类型与函数/方法要求的类型一致,这就是解引用强制多态(deref coercions)
  • 解引用强制多态发生在编译时,没有运行时性能开销
  • 解引用强制多态规则
    • T: Deref<Target=U>时从&T&U
    • T: DerefMut<Target=U>时从&mut T&mut U
    • T: Deref<Target=U>时从&mut T&U
  1. fn hello(x : &str){ println!("{}",x); };
  2. let a = MyBox::new(String::from("ABC"));
  3. hello(a.deref().deref());
  4. hello(&a);
  5. hello(&*a);
  6. //下面两句是错误的
  7. //hello(*a);
  8. //hello(a);

3 Drop特性

3.1 实现Drop特性

  1. struct CustomSmartPointer {
  2. data: String,
  3. }
  4. impl Drop for CustomSmartPointer {
  5. fn drop(&mut self) {// 注意签名: 要求&mut self
  6. println!("Dropping CustomSmartPointer with data `{}`!", self.data);
  7. }
  8. }
  9. fn main() {
  10. let c = CustomSmartPointer { data: String::from("my stuff") };
  11. let d = CustomSmartPointer { data: String::from("other stuff") };
  12. println!("CustomSmartPointers created.");
  13. }

3.2 手动调用Drop::drop()方法

  • 变量离开作用域时,Drop::drop()方法被自动调用,这个特性不能关闭
  • 不能直接调用Drop::drop()方法
  • 但可以调用std::mem::drop()函数,传入变量,实现间接调用Drop::drop()

4 Rc<T>:引用计数的智能指针

  • 使用场景:单线程、多处只读使用、无法确定哪部分最后结束使用(然后释放资源)
    • 希望在堆上分配一些内存供程序的多个部分读取,但无法在编译时确定程序的哪一部分会最后结束使用
    • 如果知道哪部分会最后结束使用,则让这部分成为数据的所有者,让其离开作用域时释放数据就可以了;其他部分使用数据的不可变引用就可以了

4.1 不使用Rc<T>定义递归结构

  1. enum List {
  2. Cons(i32, Box<List>),
  3. Nil,
  4. }
  5. let a = List::Cons(5,Box::new(List::Cons(10,Box::new(List::Nil))));
  6. let _b = List::Cons(3, Box::new(a));
  7. //编译错误:上个语句将变量a的所有权移动到b中了,a已经失效
  8. //let c = List::Cons(4, Box::new(a));

4.2 使用Rc<T>定义递归结构

Rc::clone(&a)的写法

  • 下述代码中的Rc::clone(&a)也可以写成a.clone(),但是Rust中的习惯写法是Rc::clone(&a)
  • Rc::clone(&a)强调了通过Rc进行克隆,只增加引用计数,而不进行深拷贝
  • a.clone()这种写法通常用于深拷贝的情况
  • Rc::strong_count(&a)获取引用计数、克隆会增加引用计数
  1. use std::rc::Rc;
  2. enum List {
  3. Cons(i32, Rc<List>),
  4. Nil,
  5. }
  6. let a = Rc::new(List::Cons(5,Rc::new(List::Cons(10,Rc::new(List::Nil)))));
  7. println!("a的引用计数:{}",Rc::strong_count(&a));//1
  8. let _b = List::Cons(3, Rc::clone(&a));
  9. println!("a的引用计数:{}",Rc::strong_count(&a));//2
  10. {
  11. let _c = List::Cons(4, Rc::clone(&a));
  12. println!("a的引用计数:{}",Rc::strong_count(&a));//3
  13. }
  14. println!("a的引用计数:{}",Rc::strong_count(&a));//2

4.3 Rc<T>的常用方法

  • 所有固有方法都是关联方法(没有实例方法),调用时通常写作Rc::get_mut(&mut value)的形式,而不用value.get_mut()的形式,以避免与类型T的固有方法冲突
  • clone():克隆一份实例,实际上只是增加引用计数
  • strong_count():获取引用计数
  • weak_count():获取弱引用计数
  • downgrade():获取弱引用,然后调用弱引用的upgrade()获取Option<Rc<T>>供使用
  • fn get_mut(this: &mut Rc<T>) -> Option<&mut T>:如果当前没有其他引用和弱引用,则返回内部值的可变引用;否则返回None
  • fn make_mut(this: &mut Rc<T>) -> &mut T:如果当前没有其他引用和弱引用,则返回内部值的可变引用;否则对内部值调用clone()方法,返回克隆出的值的可变引用,这就是clone-on-write

5 RefCell<T>与内部可变性

  • 对于引用和Box<T>,编译时进行借用规则检查,检查不通过时发生编译错误。这是大多数情况下的合理选择,但过于保守,除了能检查出错误的程序外,有时候还会拒绝正确的程序。
  • 对于RefCell<T>,程序运行时进行借用规则检查,检查不通过则发生panic!RefCell<T>实现了内部可变性设计(interior mutability)模式,允许通过不可变借用来修改数据(的某个字段)。
  • 要点
    1. use std::cell::RefCell
    2. 声明:sent_messages: RefCell<Vec<String>>
    3. 创建:sent_messages: RefCell::new(vec![])
    4. 使用:sent_messages.borrow_mut().push(String::from("abc"))
  • 运行时进行借用规则检查:下述代码编译通过,但运行时试图获取第二个可变引用时触发panic!
  1. let mut one_borrow = self.sent_messages.borrow_mut();
  2. let mut two_borrow = self.sent_messages.borrow_mut();
  • 以下是示例代码片段
  1. use std::cell::RefCell;
  2. pub trait Messager {
  3. fn send(&self, msg: &str);
  4. }
  5. struct MockMessenger {
  6. sent_messages: RefCell<Vec<String>>,
  7. //sent_messages: Vec<String>,
  8. }
  9. impl MockMessenger {
  10. fn new() -> MockMessenger {
  11. MockMessenger { sent_messages: RefCell::new(vec![]) }
  12. }
  13. fn demo(&self) {
  14. let mut one_borrow = self.sent_messages.borrow_mut();
  15. // 编译通过,运行时进行借用规则检查,导致panic!:资源不能同时有两个可变引用
  16. //let mut two_borrow = self.sent_messages.borrow_mut();
  17. // 这里通过不可变引用&self来修改内部字段 sent_messages,这就是内部可变性的含义
  18. one_borrow.push(String::from("abc"));
  19. //two_borrow.push(String::from("123"));
  20. }
  21. }
  22. impl Messager for MockMessenger {
  23. fn send(&self, message: &str) {
  24. // 错误:没法通过不可变的self引用来取得self.sent_messages的可变引用(push方法要求可变引用)
  25. // self.sent_messages.push(String::from(message));
  26. // 借助RefCell的borrow_mut()方法,可通过不可变的self引用取得self.sent_messages的可变引用
  27. self.sent_messages.borrow_mut().push(String::from(message));
  28. }
  29. }

6 对比

  • 所有者:Rc<T>让数据有多个所有者;Box<T>RefCell<T>让数据有单一所有者。
  • 借用检查时机:引用、Box<T>Rc<T>在编译时执行检查,检查不通过则编译不通过;RefCell<T>在运行时执行检查,检查不通过则panic!
  • RefCell<T>可在自身不可变的情况下,修改内部包含的值。
  • 可以用Rc<RefCell<T>>来获取数据的多个可变借用

6.1 编译器不能阻止引用循环和内存泄漏

  1. use std::cell::RefCell;
  2. use std::rc::Rc;
  3. #[derive(Debug)]
  4. enum List {
  5. Cons(i32, RefCell<Rc<List>>),
  6. Nil,
  7. }
  8. impl List {
  9. fn tail(&self) -> Option<&RefCell<Rc<List>>> {
  10. match *self {
  11. Cons(_, ref item) => Some(item),
  12. Nil => None,
  13. }
  14. }
  15. }
  16. use self::List::{Nil,Cons};
  17. pub fn demo() {
  18. let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
  19. println!("a initial rc count = {}", Rc::strong_count(&a));// 1
  20. println!("a next item = {:?}", a.tail());// Some(RefCell { value: Nil })
  21. let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
  22. println!("a rc count after b creation = {}", Rc::strong_count(&a));// 2
  23. println!("b initial rc count = {}", Rc::strong_count(&b));// 1
  24. println!("b next item = {:?}", b.tail());// Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
  25. if let Some(link) = a.tail() {
  26. *link.borrow_mut() = Rc::clone(&b);// 形成引用循环
  27. }
  28. println!("b rc count after changing a = {}", Rc::strong_count(&b));// 2
  29. println!("a rc count after changing a = {}", Rc::strong_count(&a));// 2
  30. // Uncomment the next line to see that we have a cycle; it will soverflow the stack
  31. //println!("a next item = {:?}", a.tail());
  32. }

6.2 避免引用循环:将Rc<T>变为Weak<T>

  • Rc::clone会增加Rc实例的strong_count,拥有所有权的Rc实例离开作用域时使得strong_count减1,若strong_count变成零,则清理引用的资源
  • 通过Rc::downgrade方法得到不代表所有权关系的弱引用指针,即Weak<T>类型的智能指针。Weak<T>对应weak_countweak_count为零不会导致清理引用的资源。
  • 调用Weak<T>实例的upgrade可得到Option<T>,若引用的资源还没有被清理,则得到Some;反之得到None
  1. use std::cell::RefCell;
  2. use std::rc::{Rc,Weak};
  3. #[derive(Debug)]
  4. struct Node {
  5. value: i32,
  6. parent: RefCell<Weak<Node>>,
  7. children: RefCell<Vec<Rc<Node>>>,
  8. }
  9. pub fn weak_ref_demo() {
  10. let leaf = Rc::new(
  11. Node {
  12. value: 100,
  13. parent: RefCell::new(Weak::new()),
  14. children: RefCell::new(vec![]),
  15. });
  16. // 100,None,1,0
  17. println!("leaf: value={} parent={:?} 强引用数={} 弱引用数={}",
  18. leaf.value, leaf.parent.borrow().upgrade(),
  19. Rc::strong_count(&leaf), Rc::weak_count(&leaf));
  20. {
  21. let branch = Rc::new(Node {
  22. value: 50,
  23. parent: RefCell::new(Weak::new()),
  24. children: RefCell::new(vec![]),
  25. });
  26. (*branch.children.borrow_mut()).push(Rc::clone(&leaf));
  27. *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
  28. // 100,Some(branch),2,0
  29. // 原生支持输出漂亮的格式
  30. /*
  31. Some(
  32. Node {
  33. value: 50,
  34. parent: RefCell {
  35. value: (Weak)
  36. },
  37. children: RefCell {
  38. value: [
  39. Node {
  40. value: 100,
  41. parent: RefCell {
  42. value: (Weak)
  43. },
  44. children: RefCell {
  45. value: []
  46. }
  47. }
  48. ]
  49. }
  50. }
  51. */
  52. println!("leaf: value={} parent={:#?} 强引用数={} 弱引用数={}",
  53. leaf.value, leaf.parent.borrow().upgrade(),
  54. Rc::strong_count(&leaf), Rc::weak_count(&leaf));
  55. // 50,1,1
  56. println!("branch: value={} 强引用数={} 弱引用数={}",
  57. branch.value, Rc::strong_count(&branch), Rc::weak_count(&branch));
  58. }
  59. // 100,None(弱引用指向的目标branch已经释放),1(branch失效时,释放对leaf的强引用),0
  60. println!("leaf: value={} parent={:?} 强引用数={} 弱引用数={}",
  61. leaf.value, leaf.parent.borrow().upgrade(),
  62. Rc::strong_count(&leaf), Rc::weak_count(&leaf));
  63. }