1
votes

I'm trying to create a HashMap containing a known value for a specific input. This input can accept multiple types, as long as they implement a certain trait. In this case, however, only a certain type is given, which Rust doesn't like.

Is there any way to "convert" a struct into a trait, or otherwise fix this issue?

#![allow(unused)]

use std::collections::HashMap;
use std::hash::*;


trait Element: Eq + PartialEq + Hash {}

trait Reaction<T: Element> {}


#[derive(Eq, Hash, PartialEq)]
struct Ion {
    charge: u16
}

impl Element for Ion {}


#[derive(Eq, Hash, PartialEq)]
struct RedoxReaction<T: Element> { left: T }

impl<T: Element> Reaction<T> for RedoxReaction<T> {}


fn get_sep_database<T: Element>() -> HashMap<RedoxReaction<T>, f32> {
    let mut map: HashMap<RedoxReaction<T>, f32> = HashMap::new();

    let reaction = RedoxReaction {
        left: Ion {
            charge: 1
        }
    };

    // note: expected type `RedoxReaction<T>`
    //       found type `RedoxReaction<Ion>`
    map.insert(reaction, 0.000 as f32);

    return map;
}


fn main() {
    let db = get_sep_database();

    let reaction = RedoxReaction {
        left: Ion {
            charge: 1
        }
    };

    // expected this to be 0.000
    let sep = db.get(&reaction);
}
2

2 Answers

2
votes

The standard solution to this problem would be to use trait objects instead of generics. Specifically, RedoxReaction would be defined as:

#[derive(Eq, Hash, PartialEq)]
struct RedoxReaction { left: Box<Element> }

However, that doesn't work here because neither PartialEq, Eq nor Hash are object safe. Indeed, it doesn't make a lot of sense to ask if two Elements are equal when one of them is an Ion and the other is a Photon.

I would instead recommend that you consider using an enum for your elements. All your elements would have the same type (the enum Element), and object safety would not be an issue.

1
votes

I eventually solved this problem by using the hash of the Reaction as the key of the HashMap with the following function:

use std::collections::hash_map::DefaultHasher;
use std::hash::*;

fn reaction_to_hash<E: Element>(reaction: &Reaction<E>) -> u64 {
    let mut s = DefaultHasher::new();
    reaction.hash(&mut s);
    s.finish()
}

Then you can use map.insert(reaction_to_hash(&reaction), <value>) to store the values, and map.get(& reaction_to_hash(&reaction)) to retrieve the value.