1

I'm coding a game with positions expressed as an Vec<u16>. I'm caching best moves and losing positions for an exhaustive dfs. Best positions return a Guess struct with a couple u16 members, while the losing positions include a single u16 reporting the depth at which it fails. I'm using a HashMap<Vec<u16>,Guess> and a HashMap<Vec<u16>,u16> to store and retrieve these. There are many overlapping functions including file saving and restoring, so I am trying to implement a generic Cache struct to manage both types as follows:

trait Cacheable {}
impl Cacheable for u16 {}
impl Cacheable for Guess {}

pub struct Cache<T: Cacheable> {
    hashmap: Vec<HashMap<Vec<u16>, T>>,
    filename: String,
    items_added: u32,
}

When loading the HashMap from disk, I need to recover the hashmap value, but I can't find a way to create a function that returns type T from the input Vec<u16>. I've tried type-specific impl like:

impl Cache<u16> {
    fn make_value(data: &Vec<u16>) -> u16 {
        data[0]
    }
}

impl Cache<Guess> {
    fn make_value(data: &Vec<u16>) -> Guess {
        Guess::new_from_vec(data)
    }
}

But the compiler complains about duplicate impl for the same function, when I attempt value = Cache::make_value(&data);. What is the idiomatic way to do this?

pms1903
  • 41
  • 3
  • Sounds like you want an enum to me: `enum CachedPosition { Winning(Guess), Losing(u16) }` or similar – kmdreko Feb 03 '23 at 20:49

2 Answers2

1

I realized I needed to implement the type-specific functions in the trait impl. I was just using the trait to limit the applicability of the generic. Instead of what I had above, I needed to do this:

trait Cacheable<T> {
    fn make_value(data: &Vec<u16>) -> T
}
impl Cacheable<u16> for u16 {
    fn make_value(data: &Vec<u16>) -> u16 {
        data[0]
    }
}
impl Cacheable<Guess> for Guess {
    fn make_value(data: &Vec<u16>) -> Guess {
        Guess::new_from_vec(data)
    }
}

Haven't tested yet, but it compiles now, so I'm optimistic. Thanks all for the input.

pms1903
  • 41
  • 3
  • No, I don't think that's what you need. There's not point in having a `Cacheable` unless `T` depends on the usage scenario. If anything, you need [associated types](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types). But in your case it's always identical to the type that implements it, so leave it out all together and return `-> Self`, meaning, the type that implements it. See my answer. – Finomnis Feb 04 '23 at 23:50
  • Note that returning `Self` is only possible if it is guaranteed to be `Sized`, so you need to add `trait Cacheable: Sized`. – Finomnis Feb 04 '23 at 23:54
0

Why do you try to write the generic-dependent make_value in an impl Cache?

You already have a trait that represents cacheable values, so use that one to represent the different behaviour. Why else would you have the trait?

Also, nitpick: Using &Vec<u16> as a function parameter is kind of an anti-pattern. Use &[u16] instead. It is completely compatible and more generic. There is no reason why one would ever need to use a &Vec<u16> instead.

use std::collections::HashMap;

#[derive(Debug)]
struct Guess {
    data: Vec<u16>,
}
impl Guess {
    fn new_from_slice(data: &[u16]) -> Self {
        Self {
            data: data.to_vec(),
        }
    }
}

pub trait Cacheable: Sized {
    fn make_value(data: &[u16]) -> Self;
}

impl Cacheable for u16 {
    fn make_value(data: &[u16]) -> u16 {
        data[0]
    }
}
impl Cacheable for Guess {
    fn make_value(data: &[u16]) -> Guess {
        Guess::new_from_slice(data)
    }
}

pub struct Cache<T: Cacheable> {
    hashmap: Vec<HashMap<Vec<u16>, T>>,
    filename: String,
    items_added: u32,
}

impl<T: Cacheable> Cache<T> {
    fn make_value(data: &[u16]) -> T {
        T::make_value(data)
    }
}

fn main() {
    let data = vec![1, 2, 3];

    let cached_u16: u16 = Cache::make_value(&data);
    let cached_guess: Guess = Cache::make_value(&data);

    dbg!(cached_u16);
    dbg!(cached_guess);
}
[src/main.rs:48] cached_u16 = 1
[src/main.rs:49] cached_guess = Guess {
    data: [
        1,
        2,
        3,
    ],
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27