Home > front end >  Struggling with Rust Generics for two very different types
Struggling with Rust Generics for two very different types

Time:02-04

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?

CodePudding user response:

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,
    ],
}

CodePudding user response:

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.

  • Related