type annotations needed, try using a fully qualified path to specify the expected types

50 Views Asked by At

I am trying to write a cache struct in rust.

use std::collections::hash_map::{self, DefaultHasher};
use std::hash::{self, Hasher};
use std::time::SystemTime;

// use hash values in a hashmap to prevent deep copy of keys
type KeyHash = u64;

pub struct WeightedCache<V> {
    map: hash_map::HashMap<KeyHash, (V, f64)>,
    last_access_time: hash_map::HashMap<KeyHash, SystemTime>,
    dirty_access_time: Vec<(KeyHash, SystemTime)>,
    max_capacity: i32,
    current_capacity: i32,
}

pub trait Cache<K, V> {
    fn new(max_capacity: i32) -> Self;
    fn get(&mut self, key: K) -> &V;
    fn put(&mut self, key: K, value: V, weight: f64);
    fn _calc_score(&self, hash_val: KeyHash) -> f64;
    fn _evict_one(&self);
    fn _hash(&self, key: &K) -> KeyHash;
}

impl<K, V> Cache<K, V> for WeightedCache<V> {
    fn new(max_capacity: i32) -> WeightedCache<V> {
        ...
    }

    fn _hash(&self, key: &K) -> KeyHash {
        ...
    }

    fn _calc_score(&self, hash_val: KeyHash) -> f64 {
       ...
    }

    fn _evict_one(&self) {
        assert!(self.current_capacity == self.max_capacity);

        let mut min_score = f64::MAX;
        let mut evict: Option<KeyHash> = None;
        for hash_key in self.map.keys() {
            let score: f64 = self._calc_score(*hash_key as KeyHash); //this line has an error
            if score < min_score {
                min_score = score;
                evict = Some(*hash_key);
            }
        }

        self.map.remove(&evict.unwrap());
        self.last_access_time.remove(&evict.unwrap());
    }

    fn get(&mut self, key: K) -> &V {
        ...
    }

    fn put(&mut self, key: K, value: V, weight: f64) {
        ...
    }
}

But there is an error at the line : self._calc_score(*hash_key as KeyHash) in fn _evict_one(&self)

The compiler shows:

error[E0282]: type annotations needed
  --> src/main.rs:63:35
   |
63 |             let score: f64 = self._calc_score(*hash_key as KeyHash);
   |                                   ^^^^^^^^^^^
   |
help: try using a fully qualified path to specify the expected types
   |
63 |             let score: f64 = <WeightedCache<V> as Cache<K, V>>::_calc_score(self, *hash_key as KeyHash);
   |                              +++++++++++++++++++++++++++++++++++++++++++++++    ~

When replace the line with what the compiler recommended, it works. Can someone help me explain why?
Is there a way to get away with it without using a long line of code e.g. can i use the same self._calc_score as before?

1

There are 1 best solutions below

1
cafce25 On BEST ANSWER

WeightedCache<V> implements Cache<K, V> for every key type K so the compiler can't figure out which one of them to pick. Neither the type WeightetCache<V> nor the signature of _calc_score(&self, KeyHash) limit's it to a single implementation.

So instead of

<WeightedCache<V> as Cache<K, V>>::_calc_score

you might want to call

<WeightedCache<V> as Cache<usize, V>>::_calc_score

or

<WeightedCache<V> as Cache<YetAnotherType, V>>::_calc_score

and there's just no way for the compiler to tell which one to pick without you helping it.

You can use:

Cache::<K, _>::_calc_score(self, *hash_key as KeyHash)

instead, which is a little shorter and does not contain redundant information.

Alternatively you could split your traits into methods that do and don't use K. Then Cache::_calc_score is unambiguous and you can use the method call syntax:

pub trait Cache {
    fn new(max_capacity: i32) -> Self;
    fn _calc_score(&self, hash_val: KeyHash) -> f64 { todo!() }
    fn _evict_one(&mut self);
}

pub trait KeyedCache<K, V>: Cache {
    fn get(&mut self, key: K) -> &V { todo!() }
    fn put(&mut self, key: K, value: V, weight: f64) { todo!() }
    fn _hash(&self, key: &K) -> KeyHash { todo!() }
}

impl<K, V> KeyedCache<K, V> for WeightedCache<V> {}

impl<V> Cache for WeightedCache<V> {
    fn new(max_capacity: i32) -> Self { todo!() }
    fn _evict_one(&mut self) {
        assert!(self.current_capacity == self.max_capacity);

        let mut min_score = f64::MAX;
        let mut evict: Option<KeyHash> = None;
        for hash_key in self.map.keys() {
            let score: f64 = self._calc_score(*hash_key as KeyHash); //this line has an error
            if score < min_score {
                min_score = score;
                evict = Some(*hash_key);
            }
        }

        self.map.remove(&evict.unwrap());
        self.last_access_time.remove(&evict.unwrap());
    }
}

But that might not be feasible depending on how exactly your methods interact.