Мутирующие поля Rc Refcell в зависимости от других его внутренних полей

Мне нужно перебрать поле структуры внутри Rc RefCell и изменить некоторые из его аргументов в соответствии с другим полем. Например, для структуры Foo:

pub struct Foo {
    pub foo1: Vec<bool>,
    pub foo2: Vec<i32>,
}

Следующий код вызывает панику:

fn main() {
    let foo_cell = Rc::new(RefCell::new(Foo { foo1: vec![true, false], foo2: vec![1, 2] }));
    foo_cell.borrow_mut().foo2.iter_mut().enumerate().for_each(|(idx, foo2)| {
        if foo_cell.borrow().foo1[idx] {
            *foo2 *= -1;
        }
    });
}

Я могу решить эту проблему, клонируя foo1 перед изменяемым заимствованием и просто используя клон внутри замыкания, но это приводит к ненужному копированию. На самом деле мне в основном нужна одна изменяемая ссылка на foo2 и одна неизменяемая ссылка на foo1 (обратите внимание, что это упрощенный код, и здесь не работает сжатие итераторов). Я понимаю, что код не соответствует правилам заимствования. Есть ли способ заставить это работать без клонирования данных?


person Adam    schedule 09.04.2021    source источник
comment
Отвечает ли это на ваш вопрос? Ошибка при попытке заимствования 2 поля из структуры, заключенной в RefCell   -  person Sven Marnach    schedule 09.04.2021


Ответы (1)


Проблема в том, что вы пытаетесь повторно заимствовать RefCell на каждой итерации цикла, в то время как он уже был заимствован для переноса итератора через foo2. Решением для этого является использование одного foo_cell.borrow_mut() перед циклом и получение отдельных заимствований для foo1 и foo2.

Обратите внимание, что для выполнения этой работы требуется разыменование foo_cell.borrow_mut().

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

pub struct Foo {
    pub foo1: Vec<bool>,
    pub foo2: Vec<i32>,
}
fn main() {
    let foo_cell = Rc::new(RefCell::new(Foo {
        foo1: vec![true, false],
        foo2: vec![1, 2]
        
    }));
    // The dereference is required to get &mut Foo out of the RefMut<Foo>
    let borrow = &mut *foo_cell.borrow_mut();
    let foo1 = &borrow.foo1;

    borrow.foo2.iter_mut().enumerate().for_each(|(idx, foo2)| {
        if foo1[idx] {
            *foo2 *= -1;
        }
    });
}

Детская площадка

person sebpuetz    schedule 09.04.2021
comment
Важнейшей частью является явный повторный заимствование &mut *. Возвращаемое значение borrow_mut() не просто ссылка, это структура RefMut, реализующая Deref и DerefMut. Каждый раз, когда вы обращаетесь к полю в структуре, вызываются методы deref() и deref_mut(), которые непрозрачны для средства проверки заимствования, поэтому он должен пометить всю структуру как заимствованную. Как только вы извлечете одну изменяемую ссылку на всю структуру, средство проверки заимствований сможет увидеть, что вы обращаетесь к отдельным полям структуры, поскольку больше не используются вызовы непрозрачных методов. - person Sven Marnach; 09.04.2021
comment
Невозможно устранить оба назначения переменных и встроить оба заимствования play .rust-lang.org / Итак, я предполагаю, что программе проверки заимствований действительно нужна небольшая дополнительная помощь, чтобы назначить let foo1 = &borrow.foo1; вне закрытия. На самом деле изменяемое заимствование может быть встроено как borrow.foo2.iter_mut().... - person sebpuetz; 09.04.2021
comment
Спасибо за объяснение, это сработало. Я ожидал, что Ref и RefMut будут работать как обычные ссылки, но я думаю, что это не так. - person Adam; 10.04.2021
comment
Вы не можете встроить оба заимствования из-за того, как работает захват переменных в замыканиях. Ссылка на &borrow.foo1 внутри замыкания приводит к захвату замыкания borrow, а не просто borrow.foo1. Существует принятый RFC Чтобы это сработало в какой-то момент в будущем. - person Sven Marnach; 11.04.2021