6
votes

I have a struct that owns a HashMap<String, String>,

struct Test {
    data: HashMap<String, String>,
}

I am trying to implement the Index trait for this type to map to the Index implementation of the hashmap (there's other logic involved so I cannot expose the hashmap).

This works if I am just getting a reference to the value in the hashmap:

impl<'b> Index<&'b str> for Test {
    type Output = String;
    fn index(&self, k: &'b str) -> &String {
        self.data.get(k).unwrap()
    }
}

However, I want to get &Option<&String> out of it, like data.get(). So I tried this:

impl<'b, 'a> Index<&'b str> for Test {
    type Output = Option<&'a String>;
    fn index(&'a self, k: &'b str) -> &Option<&'a String> {
        &self.data.get(k)
    }
}

This results in:

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
 --> <anon>:8:10
  |
8 | impl<'b, 'a> Index<&'b str> for Test {
  |          ^^ unconstrained lifetime parameter

I understand the "unconstrained lifetime parameter in 'a". Now 'a is the lifetime of Test itself, so I want (I think) where 'Self: 'a (so self lives at least as long as 'a ) . I cannot seem to figure this out for Index impl? I tried some things with adding PhantomData to my Test. But I am not getting anywhere. Any suggestions?

1
What you're trying is not possible. index() will always return a reference and you can't return a reference to the function local Option<T>, because it doesn't live long enough. Apart from that, I think (just a wild guess) that the lifetime issue is actually the same as with streaming iterators. If that's the case, Rust's type system is not powerful enough to express this yet. But anyway, you don't want to return an Option<T> via index(), as it's expected to panic on an invalid index. Just write a get() method, like the hashmap does. I hope that maybe helped.Lukas Kalbertodt
Yes, I believe you are correct. Semantically, Index should panic, so get() is the better choice.divbyzero

1 Answers

1
votes

As has been pointed out in the comments, you won't be able to do exactly what you want. But, what it seems like you really want is to replicate HashMap's get method. So I would suggest either writing your own, or implmenting Deref (and not DerefMut) to give the struct's owner immutable access directly to the internal HashMap. Hopefully that means the user can't mess up your struct's internal logic. Keep in mind that if you do both then Deref will not be used to called HashMap::get because Test::get will be available.

struct FooMap {
    data: HashMap<String, String>
}

Replicating get:

impl FooMap {
    pub fn get(&self, index: &str) -> Option<&String> { self.data.get(index) }
}

Using Deref:

impl Deref for FooMap {
    type Target = HashMap<String, String>;
    fn deref(&self) -> &Self::Target { &self.data }
}

Example code on Rust Playground