diff --git a/src/chapter_3/chapter_3_16_5.md b/src/chapter_3/chapter_3_16_5.md new file mode 100644 index 0000000..610b438 --- /dev/null +++ b/src/chapter_3/chapter_3_16_5.md @@ -0,0 +1,85 @@ +# 3.16.5 Rc智能指针 +## 1. 使用场景分析 +假定有这样一个需求,希望创建两个共享第三个列表所有权的列表,其概念类似于如下图: + +![注释](../../assets/26.png) + +根据前面的知识,可能写出来的代码如下: +```Rust +enum List { + Cons(i32, Box), + Nil, +} +use crate::List::{Cons, Nil}; +fn main() { + let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); + let b = Cons(3, Box::new(a)); + let c = Cons(4, Box::new(a)); +} +``` + +但是上面的代码报错,因为**Rust 所有权机制要求一个值只能有一个所有者**。为了解决此类需求,Rust提供了Rc智能指针。 + +## 2. 使用Rc共享数据 +Rc智能指针通过引用计数解决数据共享的问题,下面是Rc使用的简单代码: +```Rust +use std::rc::Rc; +fn main() { + let a = Rc::new(5u32); + let b = Rc::clone(&a); + let c = a.clone(); +} +``` +上面代码中,a、b、c就共享数据5。当创建b时,不会获取a的所有权,会克隆a所包含的Rc(5u32),这会使引用计数从1增加到2并允许a和b共享Rc(5u32)的所有权。 +创建c时也会克隆 a,引用计数从2增加为3。每次调用Rc::clone,Rc(5u32)的引用计数都会增加,直到有零个引用之前其数据都不会被清理。其在内存中的表示如下: + +![注释](../../assets/27.png) + +前面共享列表的需求则可以使用Rc实现如下: +```Rust +enum List { + Cons(i32, Rc), + 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))))); + let b = Cons(3, Rc::clone(&a)); + let c = Cons(4, a.clone()); //此处使用a.clone()也是ok的 +} +``` + +对应的内存示意图如下: + +![注释](../../assets/28.png) + + + +## 3. 打印Rc的引用计数 +下面的示例打印了Rc的引用计数: +```Rust +enum List { + Cons(i32, Rc), + 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!("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)); +} +``` + +使用Rc需要注意以下两点: + +- 通过Rc是允许程序的多个部分之间只读的共享数据,因为相同位置的多个可变引用可能会造成数据竞争和不一致。如果涉及到修改,需要使用RefCell或者Mutex。 +- Rc只能是同一个线程内部共享数据,它是非线程安全的。如果要在多线程中共享,需要使用Arc。 +