У меня есть тип, который хранит свои данные в контейнере за Rc<RefCell<>>
, который по большей части скрыт от общедоступного API. Например:
struct Value;
struct Container {
storage: Rc<RefCell<HashMap<u32, Value>>>,
}
impl Container {
fn insert(&mut self, key: u32, value: Value) {
self.storage.borrow_mut().insert(key, value);
}
fn remove(&mut self, key: u32) -> Option<Value> {
self.storage.borrow_mut().remove(&key)
}
// ...
}
Однако для того, чтобы заглянуть внутрь контейнера, необходимо вернуть Ref
. Этого можно добиться с помощью Ref::map()
- для пример:
// peek value under key, panicking if not present
fn peek_assert(&self, key: u32) -> Ref<'_, Value> {
Ref::map(self.storage.borrow(), |storage| storage.get(&key).unwrap())
}
Однако мне бы хотелось иметь версию peek
без паники, которая вернула бы Option<Ref<'_, Value>>
. Это проблема, потому что Ref::map
требует, чтобы вы вернули ссылку на что-то, что существует внутри RefCell
, поэтому даже если бы я хотел вернуть Ref<'_, Option<Value>>
, это не сработало бы, потому что параметр, возвращаемый storage.get()
, недолговечен.
Попытка использовать Ref::map
для создания Ref
из ранее найденного ключа также не компилируется:
// doesn't compile apparently the borrow checker doesn't understand that `v`
// won't outlive `_storage`.
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
let storage = self.storage.borrow();
if let Some(v) = storage.get(&key) {
Some(Ref::map(storage, |_storage| v))
} else {
None
}
}
Подход, который действительно работает, состоит в том, чтобы выполнить поиск дважды, но я бы очень хотел избежать этого:
// works, but does lookup 2x
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
if self.storage.borrow().get(&key).is_some() {
Some(Ref::map(self.storage.borrow(), |storage| {
storage.get(&key).unwrap()
}))
} else {
None
}
}
Компилируемый пример на игровой площадке.
Связанные вопросы, такие как this one предполагают, что внутренняя ссылка всегда доступна, поэтому у них нет этой проблемы.
Я нашел Ref::filter_map()
, который решить эту проблему, но в стабильной версии он еще не доступен, и это неясно как далеко до стабилизации. За исключением других вариантов, я бы согласился с решением, в котором используется unsafe
, при условии, что оно надежно и основано на задокументированных гарантиях.