首页 > 解决方案 > 为什么我们需要 Rc什么时候不可变引用可以完成这项工作?

问题描述

为了说明. Rc<T>_ _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));
}

然后它声称(强调我的)

我们可以更改 的定义Cons来保存引用,但是我们必须指定生命周期参数。通过指定生命周期参数,我们将指定列表中的每个元素的寿命至少与整个列表一样长。例如,借用检查器不会让我们编译let a = Cons(10, &Nil);,因为临时Nil值会在 a 可以引用它之前被删除。

嗯,不完全是。以下代码段编译在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. 此外,我可以同时指出bca这给出了a多个概念所有者(实际上是借款人)。

Rc<T>问题:当不可变引用可以完成这项工作时,为什么我们需要?

标签: rustreferenceimmutabilityborrow-checkerownership

解决方案


使用“普通”借用,您可以非常粗略地想到一个静态证明的按关系排序,其中编译器需要证明某物的所有者总是在任何借用之前复活,并且总是在所有借用死亡后死亡(a拥有String,它来了到之前b借用的生命a,然后b死去,然后a死去;有效)。对于很多用例,这是可以做到的,这是 Rust 使借用系统实用的洞察力。

在某些情况下,这不能静态完成。在您给出的示例中,您有点作弊,因为所有借款都有'static-lifetime;并且'static项目可以在任何事情之前或之后“订购”到无穷大 - 因此实际上首先没有任何限制。List<'a>当您考虑不同的生命周期(许多,List<'b>等)时,该示例变得更加复杂。当您尝试将值传递给函数并且这些函数尝试添加项目时,此问题将变得明显。这是因为在函数内部创建的值会在离开它们的作用域后消失(即当封闭函数返回时),所以我们不能在之后保留对它们的引用,否则会有悬空引用。

Rc当一个人不能静态地证明谁是原始所有者,谁的生命在任何其他人之前开始并在任何其他人之后结束时就会出现(!)。一个典型的例子是从用户输入派生的图结构,其中多个节点可以引用另一个节点。它们需要在运行时与它们所引用的节点形成“生于后死”的关系,以保证它们永远不会引用无效数据。这Rc是一个非常简单的解决方案,因为一个简单的计数器可以代表这些关系。只要计数器不为零,一些“后生,前死”的关系仍然有效。这里的关键见解是节点创建和死亡的顺序并不重要,因为任何订单有效。只有两端的点 - 计数器变为 0 - 才是真正重要的,两者之间的任何增加或减少都是相同的(0=+1+1+1-1-1-1=0与 相同0=+1+1-1+1-1-1=0Rc当计数器达到零时将被销毁。在图形示例中,这是不再引用节点的情况。这告诉它的所有者Rc(最后一个节点引用)“哦,原来是底层节点的所有者 - 没有人知道! - 我要摧毁它”。


推荐阅读