Как вернуть ссылку в аргумент функции Rc‹RefCell‹››?

Это минимальная воспроизводимая ошибка, взятая из интерпретатора, который я пишу. Насколько я понимаю, я должен иметь возможность вернуть ссылку на поле структуры в RefCell, так как 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

person Shane Murphy    schedule 30.03.2021    source источник
comment
global_object_rcc принадлежит функции get_property именно потому, что поскольку она была перемещена туда, и когда она выйдет за пределы области действия, значение, ранее известное как global_object, будет удалено. Бывают ситуации, когда Rust чрезмерно консервативен: это не одна из них. В вашем коде есть ошибка использования после освобождения, и компилятор любезно предупреждает вас об этом, а не позволяет вам вызывать UB.   -  person trentcl    schedule 31.03.2021
comment
Отвечает ли это на ваш вопрос? Как мне вернуть ссылку на что-то внутри RefCell, не нарушая инкапсуляцию?   -  person trentcl    schedule 31.03.2021
comment
Или, возможно, Как мне заимствовать RefCell‹HashMap›, найти ключ и вернуть ссылку на результат? (I я еще не дурачусь, потому что в коде есть несколько проблем, связанных с заимствованием, и я не уверен, что это основное внимание вашего вопроса)   -  person trentcl    schedule 31.03.2021


Ответы (2)


Это не может работать. borrow_mut() на RefCell возвращает RefMut, который управляет изменяемым заимствованием и гарантирует, что других не будет, пока он жив. Затем вызов get_property заимствует RefMut (неявно через deref и &mut self) и возвращает ссылку (&mut Value), которая имеет то же время жизни, что и получатель метода (&mut self). Таким образом, срок службы &mut Value зависит от того, жив ли RefMut; но он уничтожается, когда возвращается get_property, что делает ссылку недействительной.

Весь смысл RefCell (любого Cell в этом отношении) в том, что займы не могут ускользнуть. Вы можете либо попробовать закрытие, которое вызывается с помощью &mut Value; или вы можете вернуть RefMut вызывающему, с недостатком, заключающимся в том, что ваш тип не может исключить, что вызывающий удерживает его, предотвращая будущие заимствования.

person user2722968    schedule 30.03.2021
comment
Конечно, чо. Если бы я мог вернуть &mut в RefMut, который переживет его, это разрушит всю цель. - person Shane Murphy; 31.03.2021
comment
Что мне нужно помнить, так это то, что частичное изменяемое заимствование по-прежнему остается изменяемым заимствованием. - person Shane Murphy; 31.03.2021

При вызове .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() может вызвать панику!(), если вы попытаетесь заимствовать ссылку более одного раза.

person transistor    schedule 30.03.2021