Aquire &'T T из варианта<T <' a >>
Боюсь, это может быть очень просто, но я не смог понять это самостоятельно. У меня есть эта карта:
subscriptions_map: HashMap<SubscriptionKey, Subscription<'a>>
и этот вектор:
subscriptions: Vec<&'a Subscription<'a>>,
Я хочу вставить значение в HashMap
и ссылка на тот же элемент в векторе. Я пытался сделать это так:
let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
self.subscriptions.push(subs);
Но он получает эту ошибку:
error: borrowed value does not live long enough
let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: reference must be valid for the lifetime 'a as defined on the block at 40:70...
pub fn add_subscription(&'a mut self, mut item: Subscription<'a>) {
let id = item.get_id();
let _lock = self.lock.lock().unwrap();
let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
...
note: ...but borrowed value is only valid for the block suffix following statement 2 at 45:87
let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
self.subscriptions.push(subs);
}
error: aborting due to previous error
Я думаю, мой вопрос сводится к: если у меня есть Option<T<'a>>
как я могу получить &'a T
?
1 ответ
HashMap.insert()
возвращает старое значение для данного ключа, а не значение, которое вы только что передали. Это не то, что вы хотите!
После того, как вы вставили элемент в HashMap
Вы должны позвонить HashMap.get()
получить указатель на значение. Как HashMap.insert()
вступает во владение ключом и значением, мы должны передать клон id
в insert()
так что мы можем использовать оригинал id
для get()
вызов. (Если тип id
является Copy
Вы можете пропустить вызов clone()
и пусть компилятор скопирует значение.)
use std::collections::HashMap;
#[derive(Eq, PartialEq, Hash, Clone)]
struct SubscriptionKey;
struct Subscription<'a>(&'a ());
struct Foo<'a> {
subscriptions_map: HashMap<SubscriptionKey, Subscription<'a>>,
subscriptions: Vec<&'a Subscription<'a>>,
}
impl<'a> Foo<'a> {
fn add(&'a mut self, id: SubscriptionKey, item: Subscription<'a>) {
self.subscriptions_map.insert(id.clone(), item);
let subs = self.subscriptions_map.get(&id).unwrap();
self.subscriptions.push(subs);
}
}
fn main() {
let subscription_data = &();
let mut f = Foo {
subscriptions_map: HashMap::new(),
subscriptions: Vec::new(),
};
f.add(SubscriptionKey, Subscription(subscription_data));
}
Это работает нормально, но разваливается, если мы пытаемся добавить другую подписку. Если мы сделаем это:
fn main() {
let subscription_data = &();
let subscription_data2 = &();
let mut f = Foo {
subscriptions_map: HashMap::new(),
subscriptions: Vec::new(),
};
f.add(SubscriptionKey, Subscription(subscription_data));
f.add(SubscriptionKey, Subscription(subscription_data2));
}
компилятор выдает следующие сообщения:
<anon>:30:5: 30:6 error: cannot borrow `f` as mutable more than once at a time [E0499]
<anon>:30 f.add(SubscriptionKey, Subscription(subscription_data2));
^
<anon>:30:5: 30:6 help: see the detailed explanation for E0499
<anon>:29:5: 29:6 note: previous borrow of `f` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `f` until the borrow ends
<anon>:29 f.add(SubscriptionKey, Subscription(subscription_data));
^
<anon>:31:2: 31:2 note: previous borrow ends here
<anon>:20 fn main() {
...
<anon>:31 }
^
В чем дело? Почему изменчивый заем сохраняется после первого звонка Foo::add
?
Проблема исходит из определения subscriptions
поле. Это определяется как Vec<&'a Subscription<'a>>
, Удовлетворение 'a
в Subscription<'a>
это просто, так как мы получаем объект с правильным временем жизни в add
, Удовлетворение 'a
в &'a ...
сложнее, так как Subscription<'a>
значение не имеет фиксированного адреса, пока мы не вставим его в subscriptions_map
(в моем примере Subscription<'a>
перемещается из локальной переменной в main()
к параметру в Foo::add()
внутрь self.subscriptions_map
).
Для того, чтобы удовлетворить внешний 'a
, Foo::add()
должен определить его self
параметр как &'a mut self
, Если бы мы определили это как &mut self
мы не могли быть уверены, что ссылки, которые мы получаем из subscriptions_map
будет жить достаточно долго (их жизнь может быть короче 'a
).
Тем не менее, вставив &'a Subscription<'a>
внутри Foo<'a>
мы эффективно блокируем Foo
для дальнейших модификаций, так как мы сейчас храним заем от self.subscriptions_map
в self.subscriptions
, Подумайте, что произойдет, если мы вставим еще один элемент в subscriptions_map
: как мы можем быть уверены, что HashMap
не будет перемещать свои предметы в памяти? Если HashMap
действительно перемещает наш элемент, указатель в self.subscriptions
не будет обновляться автоматически и будет зависать.
Теперь предположим, что у нас есть эта ошибка remove()
метод:
impl<'a> Foo<'a> {
fn remove(&mut self, id: &SubscriptionKey) {
self.subscriptions_map.remove(id);
}
}
Этот метод компилируется нормально. Тем не менее, если мы попытались вызвать это на Foo
на котором мы звонили add()
раньше, то self.subscriptions
будет содержать висячую ссылку на элемент, который был в self.subscriptions_map
,
Таким образом, причина, почему изменяемый заем сохраняется после вызова add()
это, так как 'a
в Foo<'a>
равен времени жизни Foo<'a>
Компилятор сам видит, что объект заимствует у себя самого. Как вы знаете, у нас не может быть изменяемого заимствования и другого заимствования (изменяемого или нет), активного одновременно, поэтому Rust не позволяет нам брать изменяемый заем f
в то время как f
Сам сохраняет активный заем. На самом деле, так как мы использовали метод, который принимает self
по изменчивой ссылке, Rust предполагает, что Foo<'a>
хранит изменяемую ссылку, хотя это не так, поскольку Rust просматривает только сигнатуры для определения заимствований (это гарантирует, что изменение частного поля с &'a T
в &'a mut T
не вызывает сбоев проверки заимствований для вас и, если вы разрабатываете библиотеку, для ваших пользователей). Поскольку тип объекта никогда не меняется, Foo<'a>
заблокирован на всю оставшуюся жизнь.
Теперь, что вы можете сделать? Очевидно, вы не можете иметь Vec<&'a Subscription<'a>>
в вашей структуре. HashMap
обеспечивает values()
итератор, но он перечисляет значения в неуказанном порядке, поэтому он не поможет вам, если вы хотите перечислить значения в том порядке, в котором они были добавлены. Вместо того, чтобы использовать заимствованные указатели, вы можете использовать Rc
:
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Eq, PartialEq, Hash)]
struct SubscriptionKey;
struct Subscription<'a>(&'a ());
struct Foo<'a> {
subscriptions_map: HashMap<SubscriptionKey, Rc<Subscription<'a>>>,
subscriptions: Vec<Rc<Subscription<'a>>>,
}
impl<'a> Foo<'a> {
fn add(&mut self, id: SubscriptionKey, item: Subscription<'a>) {
let item = Rc::new(item);
self.subscriptions_map.insert(id, item.clone());
self.subscriptions.push(item);
}
}
fn main() {
let subscription_data = &();
let mut f = Foo {
subscriptions_map: HashMap::new(),
subscriptions: Vec::new(),
};
f.add(SubscriptionKey, Subscription(subscription_data));
}