RefMut从Option借用的生命周期不够长(Option<Rc<RefCell<Node>>>)。

4

首先,如果此问题以前已经被问过,请接受我的道歉。我能找到的唯一类似的问题是这个(但似乎不同):

循环引用不能存在较长时间

我的代码是:

use std::cell::RefCell;
use std::rc::Rc;

type NodePointer = Option<Rc<RefCell<Node>>>;

#[derive(Debug)]
struct Node {
    pub value : i32,
    pub next : NodePointer
}

fn main() {
    let mut vector = vec![None; 2];
    let new_node = Rc::new(RefCell::new(Node {
                value : 0,
                next : None
            }));
    vector[1] = Some(Rc::clone(&new_node));
    let new_node = Rc::new(RefCell::new(Node {
                value : 0,
                next : Some(Rc::clone(&new_node))
            }));
    vector[0] = Some(new_node);
    println!("{:?}", vector);

    // the following 3 lines would represent a body of the loop
    let mut a = vector[0].as_ref(); // Option<&Rc<RefCell<Node>>>
    let b = a.take().unwrap().borrow_mut(); // RefMut<Node>
    a = b.next.as_ref(); //ERROR : borrowed value 'b' does not live long enough

    println!("{:?}", vector);
}

下面的代码是我完整代码的一小部分。它有点奇怪,但变量'a'将用于循环遍历向量(完整代码中超过2个值)。
我试图做的是确保变量'a'被替换为向量[0]的'next'参数,而不修改向量。
编译器抱怨'b'的寿命不够长,但我不明白为什么会这样。
根据我的理解:
- 向量[0]是Option<Rc<...>> - 变量'a'是Option<&Rc<...>> - a.take() 不会 修改 vector[0] - a.take() 代替将'a'替换为None - borrow_mut() 应该给出对向量[0]的引用,但出于某种原因它没有?<- 我认为这就是问题所在 - 发生了什么事情,但我不知道发生了什么。
我知道我可以使用Option :: take()方法代替take_ref(),并且它在我的完整代码中与一些其他修改一起使用,但是我希望在两个println语句之间保持vector不被修改。
编辑: 为了提供信息,以下循环体将编译,但会修改向量...
let mut a = vector[0].take(); // Option<&Rc<RefCell<Node>>>
let temp = a.unwrap();
let mut b = temp.borrow_mut(); // RefMut<Node>
a = b.next.take(); //ERROR : borrowed value 'b' does not live long enough

1
borrow_mut() 应该给向量 [0] 的一个引用,但出于某种原因它没有。实际上,它应该返回允许您在没有对 RefCell 进行可变借用的情况下对 RefCell 的内容进行可变借用的保护对象。 - Marko Topolnik
2个回答

3

我可能错了(对借用检查器仍不是100%自信),但我认为问题出在这里:

    // b borrows from a
    let b = a.take().unwrap().borrow_mut();
    // now, a borrows from b
    // note that the lifetime of `a` doesn't magically change just because you're not using the original value anymore
    // so from the type checker's perspective, a and b borrow from each other at the same time – not allowed
    a = b.next.as_ref();
    
    // btw, if you replace `a =` with `let c =` or even `let a =`, creating a new variable with a new lifetime, it compiles
    // let a = b.next.as_ref();
    // but doesn't work for us since you want to put it in a loop

我认为这很有道理。在第一次迭代中,您从vector中借用,得到了一个Option<&Rc<_>>。但是如果在第二次迭代中你有一个Option<&Rc<_>>,那么它将从何处借用呢?您没有从vector中取走它,而是从仅在上一次迭代期间存在的东西中取走它-它可能无效。否则,您需要确保所有中间的RefMut在循环期间都能存活。

我不认为您可以从一次迭代中借用某些内容并将其带到下一次迭代。

相反,您应该利用您已经拥有的引用计数:

    let mut next = vector[0].clone();
    while let Some(node) = next {
        next = node.borrow_mut().next.clone();
    }

现在,next 的类型是 Option<Rc<_>> - 它采用了共享所有权的方式而不是借用。

我发现使用 let c 时和你一样的情况,但我的印象是可变变量的重新赋值几乎与删除先前变量并创建新变量的处理方式相同。 - Marko Topolnik
啊,我不知道。我建议你在回答中添加以下链接: https://dev59.com/fFMH5IYBdhLWcg3wsBUS 这将完善你的回答 :) - rostyslav52

2

我想进一步解释Reinis Mazeiks的答案。我稍微修改了你的代码,去掉了一些不重要的东西。

let mut a = vector[0].as_deref();
let b = a.unwrap().borrow();
a = b.next.as_deref();

这个代码会出现"b does not live long enough."的错误。变量avector中借用,而变量b也间接地从vector中借用。例如,以下代码可以编译通过:

let b: Ref<'_, Node>;
{
    let a = vector[0].as_deref();
    b = a.unwrap().borrow();
} // a is dropped
// This is okay because b's lifetime is tied to vector
println!("{}", b.value);

然而,borrow返回一个拥有值,即Ref<'_, T>。在第一个代码块的第三行中,编译器并没有看到我们从avector借用,而是看到我们从b借用。这是因为当您在代码中调用as_ref时隐式调用了Deref,它需要一个&self,因此我们只能通过Deref借用某些东西,最多只能活得和它一样长,而那个Ref仅存在于b之间。

现在我们遇到了一个棘手的问题。让我们标记一些生命周期并解包一下:

let mut a = vector[0].as_deref(); // Option<&'a RefCell<Node>>
let b = a.unwrap().borrow(); // Ref<'b, Node>
let temp = b.next.as_deref(); // Option<&'c RefCell<Node>>
a = temp;

第二行要求'a: 'b(读做'a的寿命至少与'b相同)。这是因为Refa持有的引用进行了借用,而该引用必须在b存在的时间内有效。创建temp时,我们从b借用,因此生命周期'b必须满足'b: 'c,但更重要的是'c也不能超过变量b的寿命,因为它是通过所拥有的Ref进行借用的,后者必须被丢弃。然而,第四行要求'c: 'a以遵循协变和子类型化的规则。由于我们正在将一个在'c中存在的东西分配给一个在'a中存在的东西,因此'c必须至少与'a一样长。总体而言,这就形成了一个循环说法,即'a = 'c。但是这是一个问题,因为'a'c来自不同的值,并且在不同的时间被删除,由于Ref不允许其泛型参数在其删除实现中悬空,因此我们的程序会被丢弃检查拒绝。
为了好玩,我复制了stdRefCell实现的相关部分,但是更改了BorrowRefRef所包含的)的删除实现如下:
unsafe impl<#[may_dangle] 'a> Drop for BorrowRef<'a> {
    #[inline]
    fn drop(&mut self) {
        // This is UB
        let borrow = self.borrow.get();
        debug_assert!(is_reading(borrow));
        // This is also UB
        self.borrow.set(borrow - 1);
    }
}

使用经过修改的自定义RefCell可以使三个有问题的行编译通过,验证了上面的理论论证。然而,千万不要这样做,因为drop实现使用的是'a生命周期的引用,所以这是未定义的行为,这也是你的程序被拒绝的原因。

在你的第一个代码块中,如果我们只是在第三行开头添加 letlet a = b.next.as_deref();,那么它就可以编译了。我很难理解为什么这会有所不同,因为在两种情况下,生命周期的结果应该是相同的。即使我们使用一个全新的变量 let c = ...,让 a 继续存在,它也可以编译。 - Marko Topolnik
当您将第三行更改为 let a 时,您不再分配给同一变量,这意味着不再需要最终的约束 'c: 'a。第三行中 a 的类型只是 Option<&'c RefCell<Node>>,它不会导致任何循环约束,这是问题根源。 - Ian S.
另外,在重新阅读我的回答时,我发现了一些错别字,并决定澄清一些事情,所以现在重新阅读它会更有意义。 - Ian S.
1
似乎解决方案'a == 'b == 'c是可接受的,只是当前的借用检查器不接受。在这里,使用夜间版rustc可以工作。这是链接 - Marko Topolnik
这里似乎有更多与夜间编译器相关的问题。如果我们强制 a 具有非平凡的 Drop 实现,那么它将无法编译,并出现类似于原始错误的错误。如果我们只是使 aCopy,那么它决定的放置顺序是 a,然后是 b,最后是 vector。对于这里发生的事情,我真的没有很好的解释,特别是当涉及到 aDrop 实现影响所有内容时。如果使用空的 drop impl 先删除是可以的,为什么使用非空的实现会改变呢?或许值得询问编译器团队成员。 - Ian S.
@IanS。 你知道如何吸引编译器团队的注意吗?还是我应该希望他们最终会注意到我的问题?:p Reinis给出的答案对我的情况已经足够了,但我很好奇这里发生了什么。 - rostyslav52

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接