0

I'm afraid this may be very basic but I haven't been able to figure it out on my own. I have this map:

subscriptions_map: HashMap<SubscriptionKey, Subscription<'a>>

and this vector:

subscriptions: Vec<&'a Subscription<'a>>,

I want to insert a value into the HashMap and a reference to the same item into the vector. I've tried to do it like this:

let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
self.subscriptions.push(subs);

But it gets this error:

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

I guess my question boils down to: If I have an Option<T<'a>>, how can I get a &'a T?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Peter Ludvigsen
  • 320
  • 3
  • 12
  • Possible duplicate of [Why can't I store a value and a reference to that value in the same struct?](http://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Matthieu M. Mar 22 '16 at 07:51
  • @PeterLudvigsen: What you are looking for is impossible (see http://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct). Storing one item will *permanently* anchor the parent `struct`, preventing any further borrow. – Matthieu M. Mar 22 '16 at 07:52

1 Answers1

1

HashMap.insert() returns the old value for the given key, not the value you just passed. That's not what you want!

After you've inserted an item into the HashMap, you must call HashMap.get() to retrieve a pointer to the value. As HashMap.insert() takes ownership of both the key and the value, we need to pass a clone of id to insert() so we can use the original id for the get() call. (If the type of id is Copy, you may omit the call to clone() and let the compiler copy the value.)

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));
}

This works fine, but it falls apart if we try to add another subscription. If we do this:

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));
}

the compiler gives the following messages:

<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 }
          ^

What's going on? Why does the mutable borrow persist after the first call to Foo::add?

The problem comes from the definition of the subscriptions field. It's defined as a Vec<&'a Subscription<'a>>. Satisfying the 'a in Subscription<'a> is easy, since we receive the object with the correct lifetime in add. Satisfying the 'a in &'a ... is harder, since the Subscription<'a> value doesn't have a fixed address until we insert it into subscriptions_map (in my example, a Subscription<'a> is moved from a local variable in main() to a parameter in Foo::add() to inside self.subscriptions_map).

In order to satisfy the outer 'a, Foo::add() must define its self parameter as &'a mut self. If we defined it as &mut self, we couldn't be sure that the references we get out of subscriptions_map would live long enough (their lifetime could be shorter than 'a).

However, by inserting a &'a Subscription<'a> inside of a Foo<'a>, we are effectively locking down the Foo for further modifications, since we are now storing a borrow from self.subscriptions_map in self.subscriptions. Consider what would happen if we inserted another item in subscriptions_map: how can we be sure that the HashMap won't move its items around in memory? If the HashMap does move our item, the pointer in self.subscriptions wouldn't be updated automatically and would be dangling.

Now, suppose that we have this buggy remove() method:

impl<'a> Foo<'a> {
    fn remove(&mut self, id: &SubscriptionKey) {
        self.subscriptions_map.remove(id);
    }
}

This method compiles fine. However, if we tried to call this on a Foo on which we called add() earlier, then self.subscriptions would contain a dangling reference to an item that used to be in self.subscriptions_map.

So the reason why the mutable borrow persists after calling add() is that, since the 'a in Foo<'a> is equal to the lifetime of the Foo<'a> itself, the compiler sees that the object borrows from itself. As you know, we can't have a mutable borrow and another borrow (mutable or not) active at the same time, so Rust prevents us from taking a mutable borrow on f while f itself retains an active borrow. In fact, since we used a method that takes self by mutable reference, Rust assumes that Foo<'a> stores a mutable reference, even though that's not the case, since Rust only looks at the signatures to determine borrows (this is to ensure that changing a private field from &'a T to &'a mut T doesn't cause borrow checking failures to you and, if you're developing a library, to your users). Since the type of an object never changes, the Foo<'a> is locked for the rest of its lifetime.

Now, what can you do? Clearly, you can't usefully have a Vec<&'a Subscription<'a>> in your struct. HashMap provides a values() iterator, but it enumerates the values in an unspecified order, so it won't help you if you want to enumerate the values in the order in which they were added. Instead of using borrowed pointers, you could use 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));
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155