【问题标题】:Why do we need Rc<T> when immutable references can do the job?当不可变引用可以完成这项工作时,为什么我们需要 Rc<T>?
【发布时间】:2021-08-17 05:17:39
【问题描述】:

为了说明Rc&lt;T&gt; 的必要性,the Book 提供了以下 sn-p(剧透:它不会编译)以表明如果没有 Rc&lt;T&gt;,我们将无法启用多重所有权。

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, &amp;Nil);,因为临时的 Nil 值会在 a 可以引用它之前被删除。

嗯,不完全是。以下sn -p 在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&lt;T&gt; 间接来保存嵌套的 List。此外,我可以将bc 都指向a,这给a 提供了多个概念所有者(实际上是借款人)。

问题:当不可变引用可以完成这项工作时,为什么我们需要Rc&lt;T&gt;

【问题讨论】:

  • 您当然可以这样做,但由于List它的值,您将难以从例如函数返回填充的List
  • @kmdreko 这是有道理的。现在让我试试看我能不能有类似Cons(i32, &amp;'a Box&lt;List&lt;'a&gt;&gt;),...

标签: rust reference immutability borrow-checker ownership


【解决方案1】:

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

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

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

【讨论】:

  • 因为所有借用都有'static-lifetime,你的意思是Cons(10, &amp;Nil)a 都有'static-lifetime?我不理解:它们是函数的返回值,那么它们在编译时怎么知道?
  • 是的,这就是所谓的“静态推广”。如果你做let x = &amp;42x 将是&amp;'static {integer} 类型。同样,a 带有一个'static 生命周期,因为&amp;Nil 中的借用是使用静态分配的Nil 完成的;所以Rc 解决的问题是静音。如果您考虑从外部输入创建您所描述的结构,它会变得更加明显。
  • 有趣。 “静态提升”是否意味着所有字段也是文字的文字 struct/enum 值都存储在 .TEXT 段中并给出 'static-lifetime?
  • 不一定,因为&amp;'static实际上类似于const,所以实际上,编译器要么对值进行const折叠,要么直接将其放入指令流中.但总的来说,是的,你可以把它想象成let x = &amp;Some(42),有一个Some(42)被烘焙到可执行文件中,x是一个&amp;'static
猜你喜欢
  • 1970-01-01
  • 2014-06-30
  • 2011-01-30
  • 2022-01-27
  • 2018-01-30
  • 2023-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多