Rust lifetime issue with manually dropped reference wrapper - rust

I'm trying to write a class in Rust with custom drop logic that wraps a mutable reference, but no matter what I do, I can't get the lifetimes to work out, and the compiler error messages haven't helped. Can anyone tell what I'm doing wrong, and how to fix it?
Note: I already tried every modification to this code that I could think of, such as removing or reversing the 'b: 'a constraint, but no matter what I do, the compiler produces one kind of inscrutable lifetime error message or another.
pub struct MapRef<'a, K: Eq + Hash, V>{
p: &'a mut HashMap<K, V>,
}
impl<'a, K: Eq + Hash, V> MapRef<'a, K, V> {
pub fn new(p: &'a mut HashMap<K, V>) -> Self {Self{p}}
}
impl<'a, K: Eq + Hash, V> MapRef<'a, K, V> {
pub fn reborrow<'b: 'a>(&'b mut self) -> MapRef<'b, K, V> {
Self::new(self.p)
}
}
impl<'a, K: Eq + Hash, V> Drop for MapRef<'a, K, V> {
fn drop(&mut self) {
println!("dropping ref");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() -> Result<(), String> {
let mut m: HashMap<(), ()> = HashMap::new();
let mut r1 = MapRef::new(&mut m);
{
let r2 = r1.reborrow();
}
Ok(())
}
}
Example error message:
|
37 | let r2 = r1.reborrow();
| ^^ borrowed value does not live long enough
...
41 | }
| -
| |
| `r1` dropped here while still borrowed
| borrow might be used here, when `r1` is dropped and runs the `Drop` code for type `util::MapRef`

It turns out that the reason the compiler suggests adding the erroneous 'b: 'a constraint is because the Self in Self::new is implicitly referring to the 'a lifetime. The solution is to change Self::new to MapRef::new, which allows you to drop the bad constraint.
impl<'a, K: Eq + Hash, V> MapRef<'a, K, V> {
pub fn reborrow<'b>(&'b mut self) -> MapRef<'b, K, V> {
MapRef::new(self.p)
}
}
Note that 'b can now be elided, but I left it in here for clarity.

Related

How to get around mutable and immutable references?

I'm creating a library that tries to be a Cache struct that wraps some structure that would be the component that retrieves "fresh data" for example through some HTTP requests; and also a backend field that is the actual cache backend for the data retrieved through the wrapped component.
I'm facing some problems when trying to "refresh" the data in the cache after retrieving some new data.
use std::collections::HashMap;
pub trait Cacheable<K, V>
where K: std::cmp::Eq + std::hash::Hash
{
fn get(&self, k: &K) -> Option<&V>;
fn set(&mut self, k: K, v: V);
fn del(&mut self, k: &K);
}
pub struct Cache<'a, K, V>
where K: std::cmp::Eq + std::hash::Hash
{
wrapped: &'a dyn Cacheable<K, V>,
backend: Box<dyn Cacheable<K, V>>,
}
impl<'a, K, V> Cache<'a, K, V>
where K: std::cmp::Eq + std::hash::Hash + 'static,
V: 'static,
{
fn New(wrapped: &'a dyn Cacheable<K, V>, backend_type: BackendType) -> Self {
let backend = match backend_type {
BackendType::Memory => MemoryBackend::<K, V>::New(),
};
Cache {
wrapped: wrapped,
backend: Box::new(backend),
}
}
fn get(&mut self, k: &K) -> Option<&V> {
// PROBLEM 1:
// Borrow backend here as immutable
if let Some(v) = self.backend.get(k) {
return Some(v)
}
if let Some(v) = self.wrapped.get(k) {
let cache_k = *k.clone(); // PROBLEM 2: How to clone reference value?
let cache_v = *v.clone(); // PROBLEM 2: How to clone reference value?
// PROBLEM 1:
// Borrow backend here as mutable
self.backend.set(cache_k, cache_v);
return Some(v)
}
None
}
}
pub enum BackendType {
Memory
}
pub struct MemoryBackend<K, V>
where K: std::cmp::Eq + std::hash::Hash
{
data: HashMap<K, V>,
}
impl<K, V> Cacheable<K, V> for MemoryBackend<K, V>
where K: std::cmp::Eq + std::hash::Hash
{
fn get(&self, k: &K) -> Option<&V> {
self.data.get(k)
}
fn set(&mut self, k: K, v: V) {
self.data.insert(k, v);
}
fn del(&mut self, k: &K) {
self.data.remove(k);
}
}
impl<K, V> MemoryBackend<K, V>
where K: std::cmp::Eq + std::hash::Hash
{
fn New() -> Self {
MemoryBackend {
data: HashMap::<K, V>::new(),
}
}
}
Problem 1
Possible coexistence of mutable and immutable references. What would be the best option here?
I tried changing the get method signature to return an Option<V> instead of Option<&V> but that leads me also to the problem 2, for which I can't seem to be able to clone the value of a shared reference.
Problem 2
move occurs because value has type `K`, which does not implement the `Copy` trait
help: consider borrowing here: `&*k.clone()`"
AFAIK I don't want to set Copy trait as bound of V because it might not be a simple type and would much rather clone its value than setting that as an API restriction. I can't seem to work around this.
Problem 1: Possible coexistence of mutable and immutable references. What would be the best option here? I tried changing the get method signature to return an Option<V> instead of Option<&V> but that leads me also to the problem 2, for which I can't seem to be able to clone the value of a shared reference.
You could add a get_or_insert method to the trait, and implement it for MemoryBackend using the hash_map::Entry API:
fn get_or_insert(&mut self, k: K, v: Option<V>) -> Option<&V> {
match self.data.entry(k) {
Entry::Occupied(entry) => Some(entry.into_mut()),
Entry::Vacant(entry) => v.map(|v| &*entry.insert(v)),
}
}
Or, to avoid evaluating v unless it's actually required (which an optimisation pass might already do if the above is inlined), you could instead pass in a function that returns Option<V>:
fn get_or_insert_with(&mut self, k: K, f: &dyn Fn() -> Option<V>) -> Option<&V> {
match self.data.entry(k) {
Entry::Occupied(entry) => Some(entry.into_mut()),
Entry::Vacant(entry) => f().map(|v| &*entry.insert(v)),
}
}
Note that, in order for Cacheable to be object-safe, we have to use the indirection of a function-trait object here (which, again, an optimisation pass might eliminate if the above is inlined).
Problem 2:
"move occurs because value has type K, which does not implement the Copy trait
help: consider borrowing here: &*k.clone()"
AFAIK I don't want to set Copy trait as bound of V because it might not be a simple type and would much rather clone its value than setting that as an API restriction.
So I can't seem to work around this.
You could add K: Clone and V: Clone constraints to the implementation:
impl<'a, K, V> Cache<'a, K, V>
where
K: Clone + std::cmp::Eq + std::hash::Hash + 'static,
V: Clone + 'static,
{
// etc
}
(Playground)

How do I use a &HashSet<&T> as an IntoIterator<Item = &T>?

I have a function that takes a collection of &T (represented by IntoIterator) with the requirement that every element is unique.
fn foo<'a, 'b, T: std::fmt::Debug, I>(elements: &'b I)
where
&'b I: IntoIterator<Item = &'a T>,
T: 'a,
'b: 'a,
I would like to also write a wrapper function which can work even if the elements are not unique, by using a HashSet to remove the duplicate elements first.
I tried the following implementation:
use std::collections::HashSet;
fn wrap<'a, 'b, T: std::fmt::Debug + Eq + std::hash::Hash, J>(elements: &'b J)
where
&'b J: IntoIterator<Item = &'a T>,
T: 'a,
'b: 'a,
{
let hashset: HashSet<&T> = elements.into_iter().into_iter().collect();
foo(&hashset);
}
playground
However, the compiler doesn't seem happy with my assumption that HashSet<&T> implements IntoIterator<Item = &'a T>:
error[E0308]: mismatched types
--> src/lib.rs:10:9
|
10 | foo(&hashset);
| ^^^^^^^^ expected type parameter, found struct `std::collections::HashSet`
|
= note: expected type `&J`
found type `&std::collections::HashSet<&T>`
= help: type parameters must be constrained to match other types
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
I know I could use a HashSet<T> by cloning all the input elements, but I want to avoid unnecessary copying and memory use.
If you have a &HashSet<&T> and need an iterator of &T (not &&T) that you can process multiple times, then you can use Iterator::copied to convert the iterator's &&T to a &T:
use std::{collections::HashSet, fmt::Debug, hash::Hash, marker::PhantomData};
struct Collection<T> {
item: PhantomData<T>,
}
impl<T> Collection<T>
where
T: Debug,
{
fn foo<'a, I>(elements: I) -> Self
where
I: IntoIterator<Item = &'a T> + Clone,
T: 'a,
{
for element in elements.clone() {
println!("{:?}", element);
}
for element in elements {
println!("{:?}", element);
}
Self { item: PhantomData }
}
}
impl<T> Collection<T>
where
T: Debug + Eq + Hash,
{
fn wrap<'a, I>(elements: I) -> Self
where
I: IntoIterator<Item = &'a T>,
T: 'a,
{
let set: HashSet<_> = elements.into_iter().collect();
Self::foo(set.iter().copied())
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
struct Foo(i32);
fn main() {
let v = vec![Foo(1), Foo(2), Foo(4)];
Collection::<Foo>::wrap(&v);
}
See also:
Using the same iterator multiple times in Rust
Does cloning an iterator copy the entire underlying vector?
Why does cloning my custom type result in &T instead of T?
Note that the rest of this answer made the assumption that a struct named Collection<T> was a collection of values of type T. OP has clarified that this is not true.
That's not your problem, as shown by your later examples. That can be boiled down to this:
struct Collection<T>(T);
impl<T> Collection<T> {
fn new(value: &T) -> Self {
Collection(value)
}
}
You are taking a reference to a type (&T) and trying to store it where a T is required; these are different types and will generate an error. You are using PhantomData for some reason and accepting references via the iterator, but the problem is the same.
In fact, PhantomData makes the problem harder to see as you can just make up values that don't work. For example, we never have any kind of string here but we "successfully" created the struct:
use std::marker::PhantomData;
struct Collection<T>(PhantomData<T>);
impl Collection<String> {
fn new<T>(value: &T) -> Self {
Collection(PhantomData)
}
}
Ultimately, your wrap function doesn't make sense, either:
impl<T: Eq + Hash> Collection<T> {
fn wrap<I>(elements: I) -> Self
where
I: IntoIterator<Item = T>,
This is equivalent to
impl<T: Eq + Hash> Collection<T> {
fn wrap<I>(elements: I) -> Collection<T>
where
I: IntoIterator<Item = T>,
Which says that, given an iterator of elements T, you will return a collection of those elements. However, you put them in a HashMap and iterate on a reference to it, which yields &T. Thus this function signature cannot be right.
It seems most likely that you want to accept an iterator of owned values instead:
use std::{collections::HashSet, fmt::Debug, hash::Hash};
struct Collection<T> {
item: T,
}
impl<T> Collection<T> {
fn foo<I>(elements: I) -> Self
where
I: IntoIterator<Item = T>,
for<'a> &'a I: IntoIterator<Item = &'a T>,
T: Debug,
{
for element in &elements {
println!("{:?}", element);
}
for element in &elements {
println!("{:?}", element);
}
Self {
item: elements.into_iter().next().unwrap(),
}
}
}
impl<T> Collection<T>
where
T: Eq + Hash,
{
fn wrap<I>(elements: I) -> Self
where
I: IntoIterator<Item = T>,
T: Debug,
{
let s: HashSet<_> = elements.into_iter().collect();
Self::foo(s)
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
struct Foo(i32);
fn main() {
let v = vec![Foo(1), Foo(2), Foo(4)];
let c = Collection::wrap(v);
println!("{:?}", c.item)
}
Here we place a trait bound on the generic iterator type directly and a second higher-ranked trait bound on a reference to the iterator. This allows us to use a reference to the iterator as an iterator itself.
See also:
How does one generically duplicate a value in Rust?
Is there any way to return a reference to a variable created in a function?
How do I write the lifetimes for references in a type constraint when one of them is a local reference?
There were a number of orthogonal issues with my code that Shepmaster pointed out, but to solve the issue of using a HashSet<&T> as an IntoIterator<Item=&T>, I found that one way to solve it is with a wrapper struct:
struct Helper<T, D: Deref<Target = T>>(HashSet<D>);
struct HelperIter<'a, T, D: Deref<Target = T>>(std::collections::hash_set::Iter<'a, D>);
impl<'a, T, D: Deref<Target = T>> Iterator for HelperIter<'a, T, D>
where
T: 'a,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|x| x.deref())
}
}
impl<'a, T, D: Deref<Target = T>> IntoIterator for &'a Helper<T, D> {
type Item = &'a T;
type IntoIter = HelperIter<'a, T, D>;
fn into_iter(self) -> Self::IntoIter {
HelperIter((&self.0).into_iter())
}
}
Which is used as follows:
struct Collection<T> {
item: PhantomData<T>,
}
impl<T: Debug> Collection<T> {
fn foo<I>(elements: I) -> Self
where
I: IntoIterator + Copy,
I::Item: Deref<Target = T>,
{
for element in elements {
println!("{:?}", *element);
}
for element in elements {
println!("{:?}", *element);
}
return Self { item: PhantomData };
}
}
impl<T: Debug + Eq + Hash> Collection<T> {
fn wrap<I>(elements: I) -> Self
where
I: IntoIterator + Copy,
I::Item: Deref<Target = T> + Eq + Hash,
{
let helper = Helper(elements.into_iter().collect());
Self::foo(&helper);
return Self { item: PhantomData };
}
}
fn main() {
let v = vec![Foo(1), Foo(2), Foo(4)];
Collection::<Foo>::wrap(&v);
}
I'm guessing that some of this may be more complicated than it needs to be, but I'm not sure how.
full playground

How do I return the result of get_mut from a HashMap or a HashSet?

I'm trying to wrap a HashMap, as defined below, to return a mutable reference from a HashMap:
use std::{collections::HashMap, marker::PhantomData};
struct Id<T>(usize, PhantomData<T>);
pub struct IdCollection<T>(HashMap<Id<T>, T>);
impl<'a, T> std::ops::Index<Id<T>> for &'a mut IdCollection<T> {
type Output = &'a mut T;
fn index(&mut self, id: &'a Id<T>) -> Self::Output {
self.0.get_mut(id).unwrap()
}
}
And the resulting error:
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 54:5...
--> src/id_container.rs:54:5
|
54 | / fn index(&mut self, id: &'a Id<T>) -> Self::Output {
55 | | self.0.get_mut(id).unwrap()
56 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/id_container.rs:55:9
|
55 | self.0.get_mut(id).unwrap()
| ^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 52:6...
--> src/id_container.rs:52:6
|
52 | impl<'a, T> std::ops::Index<Id<T>> for &'a mut IdCollection<T> {
| ^^
= note: ...so that the types are compatible:
expected std::ops::Index<id_container::Id<T>>
found std::ops::Index<id_container::Id<T>>
Why can't the compiler extend the lifetime of the get_mut? The IdCollection would then be borrowed mutably.
Note that I tried using a std::collections::HashSet<IdWrapper<T>> instead of a HashMap:
struct IdWrapper<T> {
id: Id<T>,
t: T,
}
Implementing the proper borrow etc. so I can use the Id<T> as a key.
However, HashSet doesn't offer a mutable getter (which makes sense since you don't want to mutate what's used for your hash). However in my case only part of the object should be immutable. Casting a const type to a non-const is UB so this is out of the question.
Can I achieve what I want? Do I have to use some wrapper such as a Box? Although I'd rather avoid any indirection...
EDIT
Ok I'm an idiot. First I missed the IndexMut instead of the Index, and I forgot the & when specifying the Self::Output in the signature.
Here's my full code below:
pub struct Id<T>(usize, PhantomData<T>);
impl<T> std::fmt::Display for Id<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, o: &Self) -> bool {
self.0 == o.0
}
}
impl<T> Eq for Id<T> {}
pub struct IdCollection<T>(HashMap<Id<T>, T>);
impl<'a, T> IntoIterator for &'a IdCollection<T> {
type Item = (&'a Id<T>, &'a T);
type IntoIter = std::collections::hash_map::Iter<'a, Id<T>, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a, T> IntoIterator for &'a mut IdCollection<T> {
type Item = (&'a Id<T>, &'a mut T);
type IntoIter = std::collections::hash_map::IterMut<'a, Id<T>, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl<T> std::ops::Index<Id<T>> for IdCollection<T> {
type Output = T;
fn index(&self, id: Id<T>) -> &Self::Output {
self.0.get(&id).unwrap()
}
}
impl<T> std::ops::IndexMut<Id<T>> for IdCollection<T> {
fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output {
self.0.get_mut(&id).unwrap()
}
}
impl<T> std::ops::Index<&Id<T>> for IdCollection<T> {
type Output = T;
fn index(&self, id: &Id<T>) -> &Self::Output {
self.0.get(id).unwrap()
}
}
impl<T> std::ops::IndexMut<&Id<T>> for IdCollection<T> {
fn index_mut(&mut self, id: &Id<T>) -> &mut Self::Output {
self.0.get_mut(id).unwrap()
}
}
If I understand correctly what you try to achieve, then I have to tell you, that it is a bit more complex than you originally thought it would be.
First of all, you have to realise, that if you like to use a HashMap then the type of the key required to be hashable and comparable. Therefore the generic type parameter T in Id<T> has to be bound to those traits in order to make Id hashable and comparable.
The second thing you need to understand is that there are two different traits to deal with the indexing operator: Index for immutable data access, and IndexMut for mutable one.
use std::{
marker::PhantomData,
collections::HashMap,
cmp::{
Eq,
PartialEq,
},
ops::{
Index,
IndexMut,
},
hash::Hash,
};
#[derive(PartialEq, Hash)]
struct Id<T>(usize, PhantomData<T>)
where T: PartialEq + Hash;
impl<T> Eq for Id<T>
where T: PartialEq + Hash
{}
struct IdCollection<T>(HashMap<Id<T>, T>)
where T: PartialEq + Hash;
impl<T> Index<Id<T>> for IdCollection<T>
where T: PartialEq + Hash
{
type Output = T;
fn index(&self, id: Id<T>) -> &Self::Output
{
self.0.get(&id).unwrap()
}
}
impl<T> IndexMut<Id<T>> for IdCollection<T>
where T: PartialEq + Hash
{
fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output
{
self.0.get_mut(&id).unwrap()
}
}
fn main()
{
let mut i = IdCollection(HashMap::new());
i.0.insert(Id(12, PhantomData), 99i32);
println!("{:?}", i[Id(12, PhantomData)]);
i[Id(12, PhantomData)] = 54i32;
println!("{:?}", i[Id(12, PhantomData)]);
}
It may seem a bit surprising, but IndexMut is not designed to insert an element into the collection but to actually modify an existing one. That's the main reason why HashMap does not implement IndexMut -- and that's also the reason why the above example uses the HashMap::insert method to initially place the data. As you can see, later on, when the value is already available we can modify it via the IdCollection::index_mut.

Wrong trait chosen based on type parameter

I have a binary trait Resolve.
pub trait Resolve<RHS = Self> {
type Output;
fn resolve(self, rhs: RHS) -> Self::Output;
}
I implemented the trait for something trivial where both arguments are taken by reference (self is &'a Foo and rhs is &'b Foo):
struct Foo;
impl <'a, 'b> Resolve<&'b Foo> for &'a Foo {
type Output = Foo;
fn resolve(self, rhs: &'b Foo) -> Self::Output {
unimplemented!()
}
}
If I now write
fn main() {
let a: &Foo = &Foo;
let b = Foo;
a.resolve(&b);
}
it will compile just fine, but if I try to implement it on my struct Signal, it will not work.
pub struct Signal<'a, T> {
ps: Vec<&'a T>,
}
impl<'a, T: Resolve<&'a T, Output = T> + 'a> Signal<'a, T> {
pub fn foo(&mut self) {
let a: &T = &self.ps[0];
let b = &self.ps[1];
a.resolve(b);
}
}
error[E0507]: cannot move out of borrowed content
--> src/main.rs:25:9
|
25 | a.resolve(b);
| ^ cannot move out of borrowed content
How do I get this example working? (playground)
The trait bound on foo only says that T implements Resolve, but you try to call .resolve() on a value of type &T.
To say, instead, that references to T must implement Resolve, you need a higher-ranked trait bound:
impl<'a, T> Signal<'a, T>
where
for<'b> &'b T: Resolve<&'a T, Output = T>,
{
pub fn foo(&mut self) { ... }
}
After thinking about this I came up with a simpler solution that does not rely on HRTB.
impl<'a, T> Signal<'a, T>
where
&'a T: Resolve<&'a T, Output = T> + 'a,
{
pub fn foo(&mut self) {
let a: &T = &self.ps[0];
let b = &self.ps[1];
a.resolve(b);
}
}
This does the same, namely describe, that &T implements Resolve, but without the need of HRTB.
You have to use the where clause for this, but apart from that this is a nice and easy solution.

Unused type parameters when binding a generic type to a trait that takes lifetime

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

Resources