简体   繁体   中英

Rust struct can't replace trait in HashMap

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM