Как вернуть ссылку на что-то внутри RefCell без нарушения инкапсуляции?

У меня есть структура, которая имеет внутреннюю изменчивость.

use std::cell::RefCell;

struct MutableInterior {
    hide_me: i32,
    vec: Vec<i32>,
}
struct Foo {
    //although not used in this particular snippet,
    //the motivating problem uses interior mutability
    //via RefCell.
    interior: RefCell<MutableInterior>,
}

impl Foo {
    pub fn get_items(&self) -> &Vec<i32> {
        &self.interior.borrow().vec
    }
}

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Vec::new(),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
}

Производит ошибку:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:16:10
   |
16 |         &self.interior.borrow().vec
   |          ^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
17 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 | /     pub fn get_items(&self) -> &Vec<i32> {
16 | |         &self.interior.borrow().vec
17 | |     }
   | |_____^

Проблема в том, что я не могу иметь функцию на Foo которая возвращает заимствованный vec, потому что заимствованный vec действителен только для времени жизни Ref, но Ref немедленно выходит из области видимости.

Я думаю, что Ref должен стоять, потому что:

RefCell<T> использует времена жизни Rust для реализации "динамического заимствования", процесс, при котором можно требовать временный, исключительный, изменяемый доступ к внутреннему значению. RefCell<T> для RefCell<T> отслеживаются "во время выполнения", в отличие от стандартных ссылочных типов Rust, которые полностью отслеживаются статически во время компиляции. Так как RefCell<T> занимает динамические данные, можно попытаться заимствовать значение, которое уже было заимствовано; когда это происходит, это приводит к панике задач.

Теперь я мог бы написать такую функцию, которая возвращает весь интерьер:

pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;

Однако это потенциально предоставляет поля (MutableInterior.hide_me в этом примере), которые действительно являются MutableInterior.hide_me реализации для Foo.

В идеале я просто хочу разоблачить сам vec, потенциально с защитой для реализации поведения динамического заимствования. Затем вызывающим абонентам не нужно узнавать о hide_me.

Ответ 1

Вы можете создать новую структуру, подобную RefCell::borrow() Ref<'a,T> возвращенной RefCell::borrow(), чтобы обернуть этот Ref и избежать его выхода из области видимости:

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}

то вы можете реализовать для Deref черту Deref, чтобы ее можно было использовать, как если бы она была &Vec<i32>:

use std::ops::Deref;

impl<'b> Deref for FooGuard<'b> {
    type Target = Vec<i32>;

    fn deref(&self) -> &Vec<i32> {
        &self.guard.vec
    }
}

после этого обновите свой get_items() чтобы вернуть экземпляр FooGuard:

impl Foo {
    pub fn get_items(&self) -> FooGuard {
        FooGuard {
            guard: self.interior.borrow(),
        }
    }
}

и Deref делает магию:

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Vec::new(),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
    let v: &Vec<i32> = &items;
}

Ответ 2

Вы можете обернуть Vec в Rc.

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

struct MutableInterior {
    hide_me: i32,
    vec: Rc<Vec<i32>>,
}
struct Foo {
    interior: RefCell<MutableInterior>,
}

impl Foo {
    pub fn get_items(&self) -> Rc<Vec<i32>> {
        self.interior.borrow().vec.clone() // clones the Rc, not the Vec
    }
}

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Rc::new(Vec::new()),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
}

Когда вам необходимо Rc::make_mut Vec, используйте Rc::make_mut для получения изменчивой ссылки на Vec. Если есть еще Rc ссылающийся на Vec, make_mut будет отделять Rc от других Rc s, клонирует Vec и сам обновляется, чтобы ссылаться на этот новый Vec, а затем дать вам изменяемую ссылку на него. Это гарантирует, что значение в другом Rc не изменится внезапно (поскольку Rc сам по себе не обеспечивает внутреннюю изменчивость).

Ответ 3

Вместо создания совершенно нового типа вы можете использовать Ref::map (начиная с Rust 1.8). Это имеет тот же результат, что и ответ Леванса:

use std::cell::Ref;

impl Foo {
    pub fn get_items(&self) -> Ref<'_, Vec<i32>> {
        Ref::map(self.interior.borrow(), |mi| &mi.vec)
    }
}

Вы также можете использовать новые функции, такие как impl Trait чтобы скрыть Ref от API:

use std::cell::Ref;
use std::ops::Deref;

impl Foo {
    pub fn get_items(&self) -> impl Deref<Target = Vec<i32>> + '_ {
        Ref::map(self.interior.borrow(), |mi| &mi.vec)
    }
}