Box
使用场景
- 链表类型,需要在数据域中放一个
Box类型指针,例如Cons list - 想把数据存在堆上,这样能使用所有权,避免在栈上拷贝数据
- 用于
**trait object**,即Box<dny TraitType>的形式
Cons list实例
#[derive(Debug)]enum List{Cons(i32,Box<List>),Nil}use self::List::{Cons,Nil};fn main() {let a = Box::new(5);println!("{}",a);let b = Cons(1,Box::new(Cons(2,Box::new(Cons(3,Box::new(Nil))))));println!("{:?}",b);// Cons(1, Cons(2, Cons(3, Nil)))}
Drop trait
概念
在值要离开作用域时执行一些代码
Drop几乎总是用于实现智能指针
离开作用域时,变量以被创建时相反的顺序被丢弃
实例
为结构体实现Drop trait
使用std::mem::drop主动丢弃变量
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("some data") };println!("CustomSmartPointer created.");drop(c);println!("CustomSmartPointer dropped before the end of main.");}
Rc
概念
Rc<T>叫做引用计数,把数据包裹在其中并存在堆上,允许栈中有多个变量指向它
用来实现变量的多所有权
但是仅适用于单线程
使用方式
创建Rc<T>类型的所有权变量let a: Rc<i32> = Rc::new(5);
通过Rc::clone创建第二个所有权变量let b: Rc<i32> = Rc::clone(&a);
使用Rc::strong_count查看引用计数println!("{}",Rc::strong_count(&a));
实例
实现下图的Cons list
#[derive(Debug)]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!("{:?}",a);// Cons(5, Cons(10, Nil))let b = Cons(3,Rc::clone(&a));let c = Cons(4,Rc::clone(&a));println!("{:?}",b);// Cons(3, Cons(5, Cons(10, Nil)))println!("{:?}",c);// Cons(4, Cons(5, Cons(10, Nil)))}
RefCell
内部可变性
内部可变性是一种设计模式,是指可以通过不可变的引用(所有权/不可变引用)拿到可变引用,Rust中使用RefCell<T>实现
RefCell<T>内部使用的是unsafe代码,但是提供了安全的API:
- 使用
borrow_mut()拿到的是可变引用,类型是RefMut<T>智能指针 - 使用
borrow()拿到的是不可变引用,类型是Ref<T>智能指针
RefCell<T>仅适用于单线程场景
运行时检查
RefCell<T>记录当前有多少个活动的Ref<T>和RefMut<T>智能指针。每次调用borrow(),RefCell<T>将活动的不可变借用计数加一。当 Ref<T> 值离开作用域时,不可变借用计数减一。
就像编译时借用规则一样,RefCell<T>在任何时候只允许有多个不可变借用或一个可变借用,只不过是在运行时才检查这个规则
与Box、Rc对比
| Box |
Rc |
RefCell |
|
|---|---|---|---|
| 所有权 | 单一 | 多个 | 单一 |
| 借用规则 | 多个不可变借用或 一个可变借用 |
只能有多个不可变借用 | 多个不可变借用或 一个可变借用 |
| 检查时机 | 编译时 | 编译时 | 运行时 |
与Rc结合使用
Rc<T>能有多所有权,但是每个所有权拿到的都是不可变引用,无法更改其内部数据
可以将RefCell<T>包在Rc<T>当中:Rc<RefCell<T>>,这时就可以先获取数据的可变引用,然后对数据进行变更
示例
一个用于发消息的库
消息trait,包括一个send()方法,指明如何发消息:
pub trait Messenger {fn send(&self, msg: &str);}
LimitTracker结构体,用来对比传入的value参数与max的差距,并调用send()方法发送消息:
struct LimitTracker<'a,T:Messenger> {messenger : &'a T,value:i32,max:i32}impl<'a,T> LimitTracker<'a, T>where T: Messenger {fn new(messenger: &T , max:i32) -> LimitTracker<T> {LimitTracker {messenger,value:0,max}}fn set_value(&mut self, value:i32){self.value = value;let percent = self.value as f64 / self.max as f64;if percent >= 1.0{self.messenger.send("more than 100%")}else if percent > 0.5 {self.messenger.send("use 50%")}else if percent > 0.2 {self.messenger.send("use 20%")}}}
测试模块
实现Messenger trait的结构体,使用Vec存储所有消息:
use super::*;use std::cell::RefCell;struct MockMessenger {msg_list:RefCell<Vec<String>>}impl MockMessenger {fn new() -> Self{MockMessenger{msg_list:RefCell::new(Vec::new()),}}}impl Messenger for MockMessenger {fn send(&self, m:&str){self.msg_list.borrow_mut().push(String::from(m));}}
LimitTracker实例,给定不同的value,发送不同的消息:
#[test]fn test_limit_tracker () {let messenger = MockMessenger::new();let mut tracker = LimitTracker::new(&messenger,100);tracker.set_value(110);tracker.set_value(70);tracker.set_value(30);println!("{:?}",messenger.msg_list);}
