如何将引用返回到一个Rc<RefCell<>>函数参数中?

4

这是我正在编写的解释器中提取的简单可重现错误。据我了解,由于RefCell具有足够的生命周期,我应该能够返回结构体字段的引用。然而,编译器告诉我不能返回当前函数拥有的值的引用,这让我感到困惑。

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

#[derive(Debug)]
enum Value {
    Number,
    String,
}

struct Object {
    pub properties: HashMap<String, Value>,
}

impl Object {
    pub fn get_property(&mut self, name: &str) -> Option<&mut Value> {
        self.properties.get_mut(name)
    }
}

fn get_property(global_object_rcc: Rc<RefCell<Object>>, name: &str) -> Option<&mut Value> {
    // Rust cannot verify that this Rc isn't the last Rc that just got moved into this function?
    global_object_rcc.borrow_mut().get_property(name)
}

fn main() {
    // Construct global object
    let mut global_object = Object {
        properties: HashMap::new(),
    };

    // Give it a property
    global_object
        .properties
        .insert("Test".to_owned(), Value::Number);

    // Put it in a Rc<RefCell> (rcc) for sharing
    let global_object_rcc = Rc::new(RefCell::new(global_object));

    // Get a reference to its property, should be valid because the reference only needs to live
    // as long as the global_object
    let property = get_property(global_object_rcc, "Test");

    dbg!(&property);
}

以下是我收到的错误信息:

error[E0515]: cannot return value referencing temporary value
  --> src\main.rs:23:5
   |
23 |     global_object_rcc.borrow_mut().get_property(name)
   |     ------------------------------^^^^^^^^^^^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     temporary value created here

1
global_object_rcc是由get_property函数拥有的,确切地说是因为它被移动到那里,当它超出范围时,先前称为global_object的值将被丢弃。在某些情况下,Rust过于保守:这不是其中之一。您的代码存在使用后释放错误,编译器正在慷慨地向您发出警报,而不是让您调用UB。 - trent
1
或者我如何借用RefCell<HashMap>,查找键并返回结果的引用?(我还没有使用重复问题跟踪工具,因为代码中存在多个借用相关问题,我不确定这是否是您问题的重点)。 - trent
2个回答

3
这样是行不通的。对于 RefCell 上的 borrow_mut(),它返回一个 RefMut,它管理可变借用,并确保在其生命周期内没有其他借用。然后调用 get_property 借用了 RefMut(隐式通过解引用和 &mut self),并返回一个具有与方法接收器(&mut self)相同生命周期的引用 (&mut Value)。因此,&mut Value 的生命周期取决于 RefMut 是否存在;但当 get_property 返回时,就会销毁它,导致该引用无效。 RefCell (以及任何 Cell)的整个重点在于,借用不能“逃脱”。您可以尝试采用一个闭包,在其中使用 &mut Value 调用;或者您可以将 RefMut 返回给调用者,缺点是您的类型无法排除调用者持有它,从而防止未来的借用。

当然,傻瓜。如果我可以将 &mut 返回到一个比它更长寿的 RefMut 中,那就违背了整个目的。 - Shane Murphy
我需要记住的是,部分可变借用仍然是可变借用。 - Shane Murphy

1
当调用.borrow_mut()时,会返回一个临时借用的引用,可以通过解引用来修改内部数据。您获得的引用将从borrow_mut()返回的时间开始生存,直到.get_property(name)返回,这不足以使引用在函数结束后继续存在。
这与Rc<RefCell<Object>>的生命周期不同,实际上被移入了调用的函数并将在函数返回时被丢弃(但Rc只会减少引用计数,只有在引用计数为0时才会删除内部数据)。引用的生命周期无法与Rc<>中的内部数据的生命周期相链接,因为该生命周期在运行时是不确定的,基于引用计数。
如果.borrow_mut()调用发生在fn get_property()之外,并且您将&mut Object传递给fn get_property(),以使其返回Option<&mut Value,那么您可以使用生命周期变量将输入引用生命周期链接到输出引用生命周期,这样就可以解决编译器错误:
fn get_property<'a>(global_object_rcc: &'a mut Object, name: &str) -> Option<&'a mut Value> { ... }

但从这个例子的外观来看,这可能不是您想要做的。

最好制作能够根据需要改变数据的函数,这样您只需在尽可能短的时间内(仅在函数内部修改数据并在不再需要引用时返回)使用.borrow_mut()借用的引用,长时间持有.borrow_mut()引用可能会导致panic!()如果您尝试多次借用该引用,则会发生错误。


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