引用计数Rc
概述:
Rc是Rust中用于实现引用计数的类型,它允许多个所有者共享同一个数据。
用法详解:
每当clone一个Rc时,引用计数增加,而每当一个Rc退出作用域时,引用计数减少。
当引用计数变为0时,Rc和它所包裹的数据都会被销毁。
Rc的clone不会进行深拷贝,指创建另一个指向包裹值的指针,并增加引用计数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 use std::rc::Rc;fn main () { let rc_examples = "Rc examples" .to_string (); { println! ("rc_a is created" ); let rc_a : Rc<String > = Rc::new (rc_examples); println! ("Reference Count of rc_a: {}" , Rc::strong_count (&rc_a)); { println! ("rc_a is cloned to rc_b" ); let rc_b : Rc<String > = Rc::clone (&rc_a); println! ("Reference Count of rc_b: {}" , Rc::strong_count (&rc_b)); println! ("Reference Count of rc_a: {}" , Rc::strong_count (&rc_a)); println! ("rc_a and rc_b are equal: {}" , rc_a.eq (&rc_b)); println! ("Length of the value inside rc_a: {}" , rc_a.len ()); println! ("Value of rc_b: {}" , rc_b); println! ("rc_b is dropped out of scope" ); } println! ("Reference Count of rc_a: {}" , Rc::strong_count (&rc_a)); println! ("rc_a is dropped out of scope" ); } }
Cell和RefCell
概述:
Rust编译器通过严格的借用规则(多个不可变引用或只有一个可变引用存在)确保程序安全性,但是会降低灵活性。因此提供了Cell和RefCell类型,允许在不可变引用的情况下修改数据。内部是通过unsafe代码实现的
Cell
概述:
Cell和RefCell在功能上没有区别,区别在于Cell<T>适用于T实现Copy的情况
示例:
1 2 3 4 5 6 7 8 9 use std::cell::Cell;fn main () { let c = Cell::new ("asdf" ); let one = c.get (); c.set ("qwer" ); let two = c.get (); println! ("{}, {}" , one, two); }
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 2 3 4 5 6 7 8 9 use std::cell::RefCell;fn main () { let s = RefCell::new (String ::from ("hello, wolrd" )); let s1 = s.borrow (); let s2 = s.borrow_mut (); println! ("{},{}" , s1, s2); }
上面这段代码不会出现编译错误,但是运行时会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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let x = Cell::new (1 );let y = &x;let z = &x; x.set (2 ); y.set (3 ); z.set (4 );println! ("{}" , x.get ());let mut x = 2 ;let y = &mut x;let z = &mut x; x = 2 ; *y = 3 ; *z = 4 ;println! ("{}" , x);
但是代码2不能编译成功,因为只能存在一个可变引用
内部可变性
概述:
对一个不可变的值进行可变借用,就是内部可变性
无内部可变性:
1 2 3 4 fn main () { let x = 5 ; let y = &mut x; }
尝试对一个不可变值进行可变借用,破坏了Rust的借用规则
RefCell应用场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 pub trait Messnger { fn send (&self , msg: String ); }struct MsgQueue { msg_cache: Vec <String >, }impl Messnger for MsgQueue { fn send (&self , msg: String ) { self .msg_cache.push (msg); } }
上面代码会编译错误,因为需要修改self的msg_cache,但是外部库的self是不可变的self,这时候RefCell就派上用场了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 use std::cell::RefCell;pub trait Messnger { fn send (&self , msg: String ); }pub struct MsgQueue { msg_cache: RefCell<Vec <String >>>, }impl Messnger for MsgQueue { fn send (&self , msg: String ) { self .msg_cache.borrow_mut ().push (msg); } }fn main () { let mq = MsgQueue { msg_cache: RefCell::new (Vec ::new ()); }; mq.send ("hello, world" .to_string ()); }
Rc+RefCell
概述:
这是一个很常见的组合,前者可以实现一个数据拥有多个所有者,后者可以实现数据的内部可变性
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 use std::cell::RefCell;use std::rc::Rc;fn main () { let s = Rc::new (RefCell::new ("Hello, wolrd" .to_string ())); let s1 = s.clone (); let s2 = s.clone (); s2.borrow_mut ().push_str (", on yeah!" ); println! ("{:?}\n{:?}\n{:?}" , s, s1, s2); }
性能损耗:
非常高,大致相当于没有线程安全版本的C++
std::shared_ptr指针。C++这个指针的主要开销也在于原子性这个并发原语上,毕竟线程安全在哪个语言开销都不小
内存损耗:
二者结合的数据结构与下面类似:
1 2 3 4 5 6 7 8 9 10 11 struct Wrapper <T> { strong_count: usize , weak_count: usize , borrow_count: isize , item: T, }
仅仅多分配了三个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 2 3 4 5 6 7 8 9 10 11 12 fn is_even (i: i32 ) -> bool { i % 2 == 0 }fn retain_even (nums: &mut Vec <i32 >) { let mut i = 0 ; for num in nums.iter ().filter (|&num| is_even (num)) { nums[i] = *num; i += 1 ; } nums.truncate (i); }
会编译错误,因为同时使用了可变借用和不可变借用
可以通过索引来解决这个问题:
1 2 3 4 5 6 7 8 9 10 fn retain_even (nums: &mut Vec <i32 >) { let mut i = 0 ; for j in 0 ..nums.len () { if is_even (nums[j]) { nums[i] = nums[j]; i += 1 ; } } nums.truncate (i); }
但这样不够最佳实践,使用迭代器才是最佳实践
可以使用上面提到的两种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 use std::cell::Cell;fn retain_even (nums: &mut Vec <i32 >) { let slice : &[Cell<i32 >] = Cell::from_mut (&mut nums[..]).as_slice_of_cells (); let mut i = 0 ; for num in slice.iter ().filter (|num| is_even (num.get ())) { slice[i].set (num.get ()); i += 1 ; } nums.truncate (i); }