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));
}
Другие вопросы по тегам