2015-04-01 5 views
8

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

use std::cell::RefCell; 

struct MutableInterior { 
    hideMe: 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 getItems(&self) -> &Vec<i32> { 
     &self.interior.borrow().vec 
    } 
} 

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

Выдает ошибку:

error: borrowed value does not live long enough 
    --> src/main.rs:16:10 
    | 
16 |   &self.interior.borrow().vec 
    |   ^^^^^^^^^^^^^^^^^^^^^^ 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 body at 15:40... 
    --> src/main.rs:15:41 
    | 
15 |  pub fn getItems(&self) -> &Vec<i32> { 
    | _________________________________________^ starting here... 
16 | |   &self.interior.borrow().vec 
17 | |  } 
    | |_____^ ...ending here 

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

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

RefCell uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCells are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in task panic.

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

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

Однако это потенциально подвергает поля (MutableInterior.hideMe в этот пример), которые действительно являются частными сведениями об осуществлении до Foo.

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

ответ

14

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

use std::cell::Ref; 

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

тогда, вы можете реализовать Deref черту для него, так что он может быть использован, как если бы это был :

use std::ops::Deref; 

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

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

после этого, сим слойные обновить ваш метод getItems() вернуть FooGuard экземпляр:

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

Deref и делает волшебство:

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

Это единственный/идиоматических способ сделать это? Кажется, что у вас немного проблемы ... Хотя я полагаю, что вместо метода getItems() вы могли бы заимствовать внутренние элементы в блоке, где он затем выходил из области видимости (или что-то еще ...) – norcalli

+2

@Norcalli В конкретном case 'RefCell', объект должен быть уведомлен, когда ссылка выходит за пределы области действия (это то, что делает деструктор' Ref'). Здесь нам нужно сохранить это поведение (ошибка OP была вызвана слишком ранним экземпляром 'Ref') и, таким образом, инкапсулировать его. – Levans