#HashSet::get (implicit) lifetime parameters too restrictive?

12 messages · Page 1 of 1 (latest)

steel bough
#

I'm trying to get something like

use std::collections::HashSet;
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct Ref<'a>(&'a ());
fn get_ref<'a>(v: (), set: &HashSet<Ref<'a>>) -> Option<Ref<'a>> {
    set.get(&Ref(&v)).copied()
}

to work (imagine there was something actually useful in the hashset instead of ()). This does not compile:

error[E0515]: cannot return value referencing function parameter `v`
 --> <source>:7:5
  |
7 |     set.get(&Ref(&v)).copied()
  |     ^^^^^^^^^^^^^--^^^^^^^^^^^
  |     |            |
  |     |            `v` is borrowed here
  |     returns a value referencing data owned by the current function

If I'm not mistaken, lifetime inference should require that the value returned by get have the same lifetime as set, but the value passed into get should be able to have any lifetime as long as it exists for the duration of that call, as get doesn't actually put it anywhere. However, this seems not to be the case, and I don't understand why this limitation exists.

Is there a way to get around this using safe code? I could just unsafe{ transmute(&v) } or equivalent but I would prefer not to if there is a better way.

steel bough
#

Rolling my own get implementation with explicitly different lifetimes for the HashSet and key doesn't seem to work because maybe Borrow forces unification of those two lifetimes?

fluid delta
# steel bough I'm trying to get something like ```rust use std::collections::HashSet; #[derive...

You're having an annoying lifetime moment.

HashSet<K> allows you to get with &Q where K: Borrow<Q>.
You have a HashSet<Ref<'a>>, and are using the blanket impl<T> Borrow<T> for T, since you're using a Ref as a key.

Catch is, Ref<'a> and Ref<'local> are not the same type, so that Borrow impl does not apply. The compiler is trying to fix this by taking the longer lifetime (which is 'a) and abbreviating it to the shorter one, so you end up calling HashSet<Ref<'local>>::get, which of course doesn't return Ref<'a>.

In principle, you'd need impl<'a, 'b> Borrow<Ref<'a>> for Ref<'b> where 'b: 'a, but since 'a can be equal to 'b, this overlaps.

There is a workaround, and it's to use hashbrown. Though it takes some, uh, creativity.

hashbrown::HashSet doesn't (only) use Borrow, instead using an Equivalent trait which you can think of as a generic version of Eq.
HashSet<K> requires K: Eq + Hash, but you can call get with Q: Equivalent<K>.
That means you can do this

#[derive(Hash)] //but we don't need Eq
struct GetRef<'a>(Ref<'a>);
impl<'a, 'b> Equivalent<Ref<'a>> for GetRef<'b> {
  fn equivalent(&self, key: &Ref<'a>) -> bool {
    self.0 == key
  }
}
```and now you should be able to call `set.get(&GetRef(Ref(&v)))`, if I'm reading the traits right (this is untested)
steel bough
#

Hm interesting, I'll try that and report back (tomorrow, gotta sleep now). I wonder if the compiler diagnostic could also be a bit clearer here? Not sure

#

Thanks!

stoic sail
#

there is another workaround, which is to define two different key types, one of which is the one stored in the set and the other which it can be borrowed as with a shorter lifetime. (it has to be two types so that the blanket implementation of std::borrow::Borrow doesn't get in the way)

#

(funny thing: this is the third time I've seen the same problem posted since yesterday, by different people)

steel bough
#

Okay so this seems to work?

use std::collections::HashSet;
use std::borrow::Borrow;

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct Ref<'a>(&'a ());

impl<'a, 'b> Borrow<&'b ()> for Ref<'a>
where
    'a: 'b,
{
    fn borrow(&self) -> &&'b () {
        &self.0
    }
}

fn get_ref<'a>(v: Ref<'a>, set: &HashSet<Ref<'a>>) -> Option<Ref<'a>> {
    set.get(&v.0).copied()
}
#

Thank you both! @stoic sail @fluid delta

fluid delta