既然不变的引用可以做这项工作,为什么我们还需要RC<;T>;? [英] Why do we need Rc<T> when immutable references can do the job?
问题描述
为了说明Rc<T>
的必要性,the Book提供了以下代码片段(剧透:它不会编译),以说明在没有Rc<T>
的情况下无法启用多重所有权。
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));
}
然后它声称(强调我的)
<2-2]>我们可以更改Cons
的定义以保存引用,但随后必须指定生存期参数。通过指定生存期参数,我们将指定列表中的每个元素至少与整个列表一样长。借用检查器不允许我们编译let a = Cons(10, &Nil);
,因为临时Nil
值将在引用它之前被删除。
嗯,不完全是。以下代码段在rustc 1.52.1
enum List<'a> {
Cons(i32, &'a List<'a>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, &Cons(10, &Nil));
let b = Cons(3, &a);
let c = Cons(4, &a);
}
请注意,通过获取引用,我们不再需要Box<T>
间接地址来保存嵌套的List
。此外,我可以将b
和c
指向a
,这将提供a
多个概念所有者(实际上是借用者)。
问题:既然不可变引用可以完成此工作,为什么我们还需要Rc<T>
?
推荐答案
您可以非常粗略地想到一个静态证明的按关系排序,其中编译器需要证明总是的所有者在任何借用之前是有生命的,并且总是在所有借入死亡之后(a
拥有String
,它在b
借入a
,然后b
死亡,然后a
死亡;有效)是有生命的。对于许多用例,这是可以做到的,这是Rust使借用系统变得实用的见解。
在某些情况下,这不能静态完成。在您给出的示例中,您有点作弊,因为所有借阅都有'static
-生存期;因此,'static
项可以在任何内容之前或之后排序到无穷大-因此实际上从一开始就没有约束。当您使用不同的生命周期(许多List<'a>
、List<'b>
等)时,该示例变得复杂得多。考虑到了。当您尝试将值传递给函数,而这些函数试图添加项时,此问题将变得明显。这是因为在函数内创建的值将在离开其作用域后死亡(即,当封闭的函数返回时),因此我们不能在以后保留对它们的引用,否则将出现悬空引用。
Rc
出现在无法静态证明谁是原始所有者的情况下,该所有者的生命周期在任何其他所有者之前开始,在任何其他所有者(!)之后结束。一个典型的例子是从用户输入派生的图结构,其中多个节点可以引用一个其他节点。它们需要在运行时与它们所引用的节点形成一种&生于后,死于";的关系,以保证它们永远不会引用无效数据。Rc
是一个非常简单解决方案,因为一个简单的计数器就可以表示这些关系。只要计数器不为零,某些在之后出生、在关系之前死亡的关系仍处于活动状态。这里的关键见解是,以什么顺序创建和终止节点并不重要,因为任何顺序都是有效的。只有两端的点-计数器达到0的点-实际上是重要的,两者之间的任何增加或减少都是相同的(0=+1+1+1-1-1-1=0
与0=+1+1-1+1-1-1=0
相同)当计数器达到零时Rc
被销毁。在图形示例中,这是指节点不再被引用。这会告诉Rc
(引用的最后一个节点)的所有者&qot;哦,结果是我是基础节点的所有者-没有人知道!-我可以销毁它";。
这篇关于既然不变的引用可以做这项工作,为什么我们还需要RC<;T>;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!