rc<T>
,引用计数智能指针
在大多数情况下,所有权是明确的:您确切地知道哪个变量 拥有给定的值。但是,在某些情况下,单个值可能具有 多个所有者。例如,在图形数据结构中,多个边可能会 指向同一节点,并且该节点在概念上由所有边拥有 指向它。除非节点没有任何 edges 指向它,因此没有所有者。
您必须使用 Rust type 显式启用多个所有权,该类型是引用计数的缩写。类型
跟踪对值的引用数以确定
该值仍在使用中。如果对某个值的引用为零,则值
可以清理而不会使任何引用无效。Rc<T>
Rc<T>
想象一下家庭活动室里的电视。当一个人进来看电视时,
他们打开了它。其他人可以进入房间看电视。当最后一个
人们离开房间,他们关掉了电视,因为它不再被使用。
如果有人在其他人仍在观看电视时关闭电视,就会有
其余电视观众的哗然!Rc<T>
当我们想在堆上为
要读取程序的多个部分,并且我们在编译时无法确定
哪个部分将最后完成对数据的使用。如果我们知道哪个部分会完成
最后,我们可以将这部分设为数据的所有者,而 Normal 所有权
在编译时强制执行的规则将生效。Rc<T>
请注意,这仅适用于单线程方案。当我们讨论
并发性,我们将介绍如何在
多线程程序。Rc<T>
使用 Rc<T>
共享数据
让我们回到示例 15-5 中的 cons 列表示例。回想一下,我们定义了
它使用 .这一次,我们将创建两个共享所有权的列表
第三个列表。从概念上讲,这类似于图 15-3:Box<T>
我们将创建包含 5 个和 10 个的列表。然后我们再制作两个
lists:以 3 开头,以 4 开头。然后,这两个 and 列表将继续到包含 5 和 10 的第一个列表。在其他
words,则两个列表将共享包含 5 和 10 的第一个列表。a
b
c
b
c
a
尝试使用我们的 with 定义来实现这个场景是行不通的,如示例 15-17 所示:List
Box<T>
文件名: src/main.rs
enum List {
Cons(i32, Box<List>),
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));
}
当我们编译此代码时,我们收到此错误:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0382]: use of moved value: `a`
--> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `cons-list` (bin "cons-list") due to 1 previous error
变体拥有它们持有的数据,因此当我们创建列表时,将移动到 并拥有 .然后,当我们尝试再次使用 when
正在创建 ,我们不允许这样做,因为已被移动。Cons
b
a
b
b
a
a
c
a
我们可以将 的定义更改为 hold 引用,但随后
我们必须指定生命周期参数。通过指定生命周期
参数,我们将指定列表中的每个元素都将位于
至少与整个列表一样长。元素和列表就是这种情况
在示例 15-17 中,但并非在每个场景中。Cons
相反,我们将 的定义更改为 use 来代替 ,如示例 15-18 所示。现在,每个变体都将包含一个值
以及指向 .当我们创建 时,而不是采用
的 所有权 ,我们将克隆 持有的 ,从而
将引用数量从 1 增加到 2,并 LET 并共享该 .我们还会在
creating ,将引用数量从 2 个增加到 3 个。每次
我们调用 ,将 WILL 中数据的引用计数
increase,除非对
它。List
Rc<T>
Box<T>
Cons
Rc<T>
List
b
a
Rc<List>
a
a
b
Rc<List>
a
c
Rc::clone
Rc<List>
文件名: src/main.rs
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))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }
我们需要添加一个语句以纳入范围,因为它不是
在序曲中。在 中,我们创建包含 5 和 10 的列表并将其存储在
新 .然后,当我们创建 and 时,我们调用函数并将对 in 的引用作为
论点。use
Rc<T>
main
Rc<List>
a
b
c
Rc::clone
Rc<List>
a
我们本可以调用 而不是 ,但 Rust 的
convention 在这种情况下使用。的实现不会像大多数类型那样对所有数据进行深层复制”
do 的实现。对 的调用仅递增
reference count 的 Count,这不会花费太多时间。数据的深层副本可以采用
很多时间。通过使用 for reference counting,我们可以直观地
区分深拷贝类型的克隆和以下类型的克隆
增加引用计数。在
代码中,我们只需要考虑深拷贝克隆,并且可以忽略对 .a.clone()
Rc::clone(&a)
Rc::clone
Rc::clone
clone
Rc::clone
Rc::clone
Rc::clone
克隆 Rc<T>
会增加引用计数
让我们更改示例 15-18 中的工作示例,以便我们可以看到参考
计数会发生变化,因为我们创建和删除对 in 的引用。Rc<List>
a
在示例 15-19 中,我们将更改它,使其在 list 周围有一个内部作用域;
然后我们可以看到 reference count 在超出范围时如何变化。main
c
c
文件名: src/main.rs
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!("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)); }
在程序中引用计数发生变化的每个点,我们都会打印
reference count 的 c.这
函数被命名,而不是因为
还有一个 ;我们将在 “防止参考循环:将 Rc<T>
变成 Weak<T>
一节中看到它的用途。Rc::strong_count
strong_count
count
Rc<T>
weak_count
weak_count
此代码打印以下内容:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s
Running `target/debug/cons-list`
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
我们可以看到 in 的初始引用计数为 1;然后
每次调用 时,计数都会增加 1。当超出范围时,
计数减少 1。我们不必调用函数来减少
引用计数,就像我们必须调用以增加引用一样
count:特征的实现会减少引用计数
当值超出范围时自动进行。Rc<List>
a
clone
c
Rc::clone
Drop
Rc<T>
在这个例子中,我们看不到的是 when 和 then 超出范围
在 的末尾,计数为 0,并清理
完全。Using 允许单个值具有多个所有者,并且
count 可确保只要任何所有者
仍然存在。b
a
main
Rc<List>
Rc<T>
通过不可变引用,允许您在多个
程序的部件仅用于读取。如果允许,您可以有多个
可变引用,则可能会违反所讨论的借用规则之一
在第 4 章中:对同一位置的多个可变借用会导致数据竞争
和不一致。但是能够更改数据非常有用!在下一个
部分中,我们将讨论内部可变性模式和类型,你可以将其与 an 结合使用
不可变性限制。Rc<T>
Rc<T>
RefCell<T>
Rc<T>
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准