0
use std::collections::HashMap;
use std::hash::Hash;

struct Watchable<'a, K, V, W: Watcher<'a, K, V>> {
    data: HashMap<K, V>,
    watchers: Vec<W>,
}

trait Watcher<'a, K, V> {
    fn before_new(&mut self, key: &'a K, value: &'a V);
}

struct IndexWatcher<'a, I: 'a, V: 'a> {
    data: HashMap<&'a I, &'a V>,
    indexer: fn(&V) -> &I,
}

impl<'a, K, V, I> Watcher<'a, K, V> for IndexWatcher<'a, I, V>
    where I: Eq + Hash
{
    fn before_new(&mut self, key: &'a K, value: &'a V) {
        let index = (self.indexer)(value);
        self.data.insert(index, value);
    }
}
error[E0392]: parameter `'a` is never used
 --> src/main.rs:4:18
  |
4 | struct Watchable<'a, K, V, W: Watcher<'a, K, V>> {
  |                  ^^ unused type parameter
  |
  = help: consider removing `'a` or using a marker such as `std::marker::PhantomData`

Is there any way to remove some lifetime annotation? It seems everything has the same lifetime a.

At first, I didn't put any specific lifetime:

struct IndexWatcher<I, V> {
    data: HashMap<&I, &V>,
    indexer: fn(&V) -> &I,
}

The compiler complained about lifetimes, so I added it:

struct IndexWatcher<'a, I: 'a, V: 'a> {
    data: HashMap<&'a I, &'a V>,
    indexer: fn(&V) -> &I,
}

When I tried to implement the trait without lifetimes:

trait Watcher<K, V> {
    fn before_new(&mut self, key: &K, value: &V);
}

impl<'a, K, V, I> Watcher<K, V> for IndexWatcher<'a, I, V>
    where I: Eq + Hash
{
    fn before_new(&mut self, key: &K, value: &V) {
        let index = (self.indexer)(value);
        self.data.insert(index, value);
    }
}

I got the error:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> <anon>:18:33
   |
18 |         self.data.insert(index, value);
   |                                 ^^^^^
   |

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> <anon>:17:21
   |
17 |         let index = (self.indexer)(value);
   |                     ^^^^^^^^^^^^^^^^^^^^^
   |

Hence my final version with lifetimes everywhere.

Ideally, I would like to use the trait in Watchable like the following:

impl<K, V, W: Watcher<K, V>> Watchable<K, V, W>
    where K: Eq + Hash {

    fn insert(&mut self, k: K, v: V) -> Option<V> {
        match self.data.entry(k) {
            Occupied(mut occupied) => {
                {
                    let k = occupied.key();
                    let old = occupied.get();
                    for watcher in &mut self.watchers {
                        watcher.before_change(k, &v, old);
                    }
                }
                let old = occupied.insert(v);
                Some(old)
            },
            Vacant(vacant) => {
                {
                    let k = vacant.key();
                    for watcher in &mut self.watchers {
                        watcher.before_new(k, &v);
                    }
                }
                vacant.insert(v);
                None
            }
        }
    }
}

trait Watcher<K, V> {
    fn before_new(&mut self, key: &K, value: &V);
    fn before_change(&mut self, key: &K, value: &V, old: &V);
}
colinfang
  • 20,909
  • 19
  • 90
  • 173
  • Why have you placed the generic types (`'a, K, V`) on the *trait* instead of the function? Why do you have an explicit lifetime at all? – Shepmaster Apr 28 '17 at 16:29
  • @Shepmaster Sry. I was used to other languages which encourage code snippets without `import / using` – colinfang Apr 28 '17 at 16:30
  • It's fine, but including the imports (but minimizing them!) reduces the amount of guessing that the answerer has to do to reproduce the same problem. There's no guarantee that you are using the standard `HashMap`, for example; it could be one from a crate. – Shepmaster Apr 28 '17 at 16:33
  • I think what @ildjarn is trying to ask is: could you add a small `main` method that shows how these structs / traits would be used? I have a potential solution, but I don't know if it actually works in your case. – Shepmaster Apr 28 '17 at 16:37
  • @ildjarn I think you answered quite a few my F# questions years ago! E.g. (http://stackoverflow.com/questions/12521841/is-there-any-way-to-simplify-this-line/12522040#12522040) – colinfang Apr 28 '17 at 16:47
  • Aha, I knew your name was familiar... ;-D – ildjarn Apr 28 '17 at 16:49
  • @Shepmaster I updated the question, hope it makes a bit clearer – colinfang Apr 28 '17 at 16:54

1 Answers1

2

You can get rid of the error by using a higher-rank trait bound instead of a lifetime parameter:

struct Watchable<K, V, W: for<'a> Watcher<'a, K, V>> {
    data: HashMap<K, V>,
    watchers: Vec<W>,
}

That doesn't solve your problem though. IndexWatcher will not satisfy the bound for<'a> Watcher<'a, K, V> because a IndexWatcher<'a, I, V> only implements Watcher<'a, K, V> for one specific lifetime, not for all possible lifetimes.

Fundamentally, the problem is that you're trying to put a value and a reference to that value in the same struct (indirectly). That is, your idea is that the watchers are expected to borrow data from the Watchable, but the Watchable also owns the watchers. Please take the time to read this question and Shepmaster's answer to understand why that idea is not going to work.

In particular, be aware that inserting an entry in or removing an entry from the Watchable's HashMap might invalidate the references in any of the watchers due to HashMap needing to reallocate storage, which may cause the address of the keys and values to change.

What I would do instead is wrap the keys and values in an Rc (or an Arc if you want to share a Watchable across threads). Getting a shared index value out of a Rc<V> might be problematic though. Consider changing your index function to fn(&V) -> I, and have the index functions return clones (they can be clones of an Rc if cloning the index value is too expensive) or handles.

Community
  • 1
  • 1
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155