Rust中的Rc. Cell, RefCell

宁睿  论坛元老 | 2025-1-17 01:28:06 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1082|帖子 1082|积分 3256

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引用计数Rc

概述: Rc是Rust中用于实现引用计数的类型,它允许多个所有者共享同一个数据。
用法详解:


  • 每当clone一个Rc时,引用计数增长,而每当一个Rc退出作用域时,引用计数淘汰。
  • 当引用计数变为0时,Rc和它所包裹的数据都会被销毁。
  • Rc的clone不会进行深拷贝,指创建另一个指向包裹值的指针,并增长引用计数
示例:
  1. use std::rc::Rc;
  2. fn main() {
  3.     let rc_examples = "Rc examples".to_string();
  4.     {
  5.         println!("rc_a is created");
  6.         
  7.         let rc_a: Rc<String> = Rc::new(rc_examples);
  8.         println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
  9.         {
  10.             println!("rc_a is cloned to rc_b");
  11.             let rc_b: Rc<String> = Rc::clone(&rc_a);
  12.             println!("Reference Count of rc_b: {}", Rc::strong_count(&rc_b));
  13.             println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
  14.             println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b));
  15.             // 可以直接使用值的方法
  16.             println!("Length of the value inside rc_a: {}", rc_a.len());
  17.             // 直接使用值
  18.             println!("Value of rc_b: {}", rc_b);
  19.             println!("rc_b is dropped out of scope");
  20.         }
  21.         println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
  22.         println!("rc_a is dropped out of scope");
  23.     }
  24. }
复制代码
Cell和RefCell

概述: Rust编译器通过严酷的借用规则(多个不可变引用或只有一个可变引用存在)确保程序安全性,但是会降低机动性。因此提供了Cell和RefCell类型,允许在不可变引用的情况下修改数据。内部是通过unsafe代码实现的
Cell

概述: Cell和RefCell在功能上没有区别,区别在于Cell<T>实用于T实现Copy的情况
示例:
  1. use std::cell::Cell;
  2. fn main() {
  3.     let c = Cell::new("asdf");
  4.     let one = c.get();
  5.     c.set("qwer");
  6.     let two = c.get();
  7.     println!("{}, {}", one, two);
  8. }
复制代码
asdf是&str类型,实现了Copy trait,取到值保存在one变量后,还能同时进行修改,这个违背了Rust的借用规则,但通过Cell就能做到这一点
  1. let c = Cell::new(String::from("asdf"));
复制代码
这段代码编译器会报错,因为String没有实现Copy trait
RefCell

Rust规则智能指针带来的额外规则一个数据只有一个所有者Rc/Arc让一个数据可以拥有多个所有者要么多个不可变借用,要么一个可变借用RefCell实现编译器可变、不可变引用共存违背规则导致编译错误违背规则导致运行时panic
  1. use std::cell::RefCell;
  2. fn main() {
  3.     let s = RefCell::new(String::from("hello, wolrd"));
  4.     let s1 = s.borrow();
  5.     let s2 = s.borrow_mut();
  6.    
  7.     println!("{},{}", s1, s2);
  8. }
复制代码
上面这段代码不会出现编译错误,但是运行时会panic
RefCell为何存在?
从上面看,通过RefCell并不能绕过rust的借用规则,那另有什么用?
对于大型的复杂程序,可以选择使用RefCell来让事变简化。在Rust编译器的ctxt结构体中有大量的RefCell类型的map字段,主要原因是这些map会被分散在各个地方的代码片断所广泛使用或修改,很容易就遇到编译器跑出来的各种错误,但是你不知道如何解决。这时候可以使用RefCell,在运行时发现这些错误,因为一旦有的代码使用不精确,就会panic,我们就知道那里借用冲突了。
Cell or RefCell

主要区别:


  • Cell只实用于实现了Copy trait类型,用于提供值,而RefCell用于提供引用
  • Cell不会panic,而RefCell会在运行时panic
性能比力:
Cell没有额外的开销,下面两段代码的性能是一样的
  1. // code 1
  2. let x = Cell::new(1);
  3. let y = &x;
  4. let z = &x;
  5. x.set(2);
  6. y.set(3);
  7. z.set(4);
  8. println!("{}", x.get());
  9. // code 2
  10. let mut x = 2;
  11. let y = &mut x;
  12. let z = &mut x;
  13. x = 2;
  14. *y = 3;
  15. *z = 4;
  16. println!("{}", x);
复制代码
但是代码2不能编译成功,因为只能存在一个可变引用
内部可变性

概述: 对一个不可变的值进行可变借用,就是内部可变性
无内部可变性:
  1. fn main() {
  2.     let x = 5;
  3.     let y = &mut x;
  4. }
复制代码
实行对一个不可变值进行可变借用,破坏了Rust的借用规则
RefCell应用场景:
  1. // 定义在外部库的trait
  2. pub trait Messnger {
  3.     fn send(&self, msg: String);
  4. }
  5. // 我们自己写的代码
  6. struct MsgQueue {
  7.     msg_cache: Vec<String>,
  8. }
  9. impl Messnger for MsgQueue {
  10.     fn send(&self, msg: String) {
  11.         self.msg_cache.push(msg);
  12.     }
  13. }
复制代码
上面代码会编译错误,因为需要修改self的msg_cache,但是外部库的self是不可变的self,这时候RefCell就派上用场了。
  1. use std::cell::RefCell;
  2. pub trait Messnger {
  3.         fn send(&self, msg: String);
  4. }
  5. pub struct MsgQueue {
  6.     msg_cache: RefCell<Vec<String>>>,
  7. }
  8. impl Messnger for MsgQueue {
  9.     fn send(&self, msg: String) {
  10.         self.msg_cache.borrow_mut().push(msg);
  11.     }
  12. }
  13. fn main() {
  14.     let mq = MsgQueue {
  15.         msg_cache: RefCell::new(Vec::new());
  16.     };
  17.     mq.send("hello, world".to_string());
  18. }
复制代码
Rc+RefCell

概述: 这是一个很常见的组合,前者可以实现一个数据拥有多个所有者,后者可以实现数据的内部可变性
示例:
  1. use std::cell::RefCell;
  2. use std::rc::Rc;
  3. fn main() {
  4.     let s = Rc::new(RefCell::new("Hello, wolrd".to_string()));
  5.    
  6.     let s1 = s.clone();
  7.     let s2 = s.clone();
  8.    
  9.     s2.borrow_mut().push_str(", on yeah!");
  10.    
  11.     println!("{:?}\n{:?}\n{:?}", s, s1, s2);
  12. }
复制代码
性能损耗:
非常高,大致相当于没有线程安全版本的C++ std::shared_ptr指针。C++这个指针的主要开销也在于原子性这个并发原语上,毕竟线程安全在哪个语言开销都不小
内存损耗:
二者结合的数据结构与下面雷同:
  1. struct Wrapper<T> {
  2.     // Rc
  3.     strong_count: usize,
  4.     weak_count: usize,
  5.    
  6.     // RefCell
  7.     borrow_count: isize,
  8.    
  9.     // 包裹的数据
  10.     item: T,
  11. }
复制代码
仅仅多分配了三个usize/isize
CPU损耗:
从CPU来看, 损耗如下:


  • 对Rc<T>解引用是免费的(编译期),但是*带来的间接取值并不免费
  • clone Rc<T>需要将当前的引用计数跟0和usize::Max进行一次比力,然后将计数值加1
  • drop Rc<T>需要将计数值减1,然后跟0进行一次比力
  • 对RefCell进行不可变借用,需要将isize类型的借用计数加1,然后跟0进行比力
  • 对RefCell的不可变借用进行开释,需要将isize减1
  • 对RefCell的可变借用大致跟上面差不多,但需要先跟0比力,然后再减1
  • 对RefCell的可变借用进行开释,需要将isize加1
解决借用冲突

两种方法:


  • Cell::from_mut,将&mut T转换为Cell<T>
  • Cell::as_slice_of_cells,将&Cell<T>转换为&[Cell<T>]
常见的借用冲突题目:
  1. fn is_even(i: i32) -> bool {
  2.     i % 2 == 0
  3. }
  4. fn retain_even(nums: &mut Vec<i32>) {
  5.     let mut i = 0;
  6.     for num in nums.iter().filter(|&num| is_even(num)) {
  7.         nums[i] = *num;
  8.         i += 1;
  9.     }
  10.     nums.truncate(i);
  11. }
复制代码
会编译错误,因为同时使用了可变借用和不可变借用
可以通过索引来解决这个题目:
  1. fn retain_even(nums: &mut Vec<i32>) {
  2.     let mut i = 0;
  3.     for j in 0..nums.len() {
  4.         if is_even(nums[j]) {
  5.             nums[i] = nums[j];
  6.             i += 1;
  7.         }
  8.     }
  9.     nums.truncate(i);
  10. }
复制代码
但这样不敷最佳实践,使用迭代器才是最佳实践
可以使用上面提到的两种方法:
  1. use std::cell::Cell;
  2. fn retain_even(nums: &mut Vec<i32>) {
  3.     let slice: &[Cell<i32>] = Cell::from_mut(&mut nums[..]).as_slice_of_cells();
  4.    
  5.     let mut i = 0;
  6.     for num in slice.iter().filter(|num| is_even(num.get())) {
  7.         slice[i].set(num.get());
  8.         i += 1;
  9.     }
  10.    
  11.     nums.truncate(i);
  12. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表