jeje
jeje

Reputation: 3211

Lifetime in mutable structure with HashSet

I'm having trouble understanding why rust doesn't like my remove_str method in there:

use std::cell::RefCell;
use std::collections::HashSet;


#[derive(Hash, Eq, PartialEq)]
struct StringWrap<'a>{
    s: &'a String,
}

struct Container<'a>{
    m: HashSet<StringWrap<'a>>
}

impl<'a> Container<'a>{
    fn remove_str(&mut self, s: &str){
        let string = String::from(s);
        let to_remove = StringWrap{s: &string};
        self.m.remove(&to_remove);
    }
}

It chokes with:

error[E0597]: `string` does not live long enough
  --> tests/worksheet.rs:17:39
   |
14 | impl<'a> Container<'a>{
   |      -- lifetime `'a` defined here
...
17 |         let to_remove = StringWrap{s: &string};
   |                                       ^^^^^^^ borrowed value does not live long enough
18 |         self.m.remove(&to_remove);
   |         ------------------------- argument requires that `string` is borrowed for `'a`
19 |     }
   |     - `string` dropped here while still borrowed

As far as I can see, my string and to_remove live long enough to allow the .remove call to do its job. Is it because remove is potentially asynchronous or something like that?

Thanks for any help or insight!

Upvotes: 4

Views: 388

Answers (2)

imoc
imoc

Reputation: 23

In context of HashSet<T>::remove/take/get(arg1: &Q):
These call internally will try to get "T's borrowed version Q from inside the HashSet<T> itself" to compare to arg1 by Borrow<Q> trait for T. So a custom impl Borrow<Q> for T with appropriate lifetime is a good idea.

Upvotes: -1

eggyal
eggyal

Reputation: 125865

As far as I can see, my string and to_remove live long enough to allow the .remove call to do its job. Is it because remove is potentially asynchronous or something like that?

No, it's because HashSet::remove must be called with something that the item becomes when borrowed:

pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool
where
    T: Borrow<Q>,
    Q: Hash + Eq, 

However, unless you manually implement Borrow for StringWrap, only the blanket reflexive implementation will apply—and thus remove can only be called with value of type &StringWrap<'a>. Note the lifetime requirement.

What you need to do to make this work is to implement Borrow for StringWrap. You could, for example, do the following:

impl Borrow<str> for StringWrap<'_> {
    fn borrow(&self) -> &str {
        self.s
    }
}

and then Container::remove_str can merely forward its argument to HashMap::remove:

impl Container<'_> {
    fn remove_str(&mut self, s: &str) {
        self.m.remove(s);
    }
}

See it on the playground.

All that said, it's rather unusual to store references in a HashSet: typically one would move ownership of the stored Strings into the set, which would render this problem moot as no lifetimes would be at play.

Upvotes: 4

Related Questions