Home > Back-end >  Get HashMap<K, V>'s V as a type
Get HashMap<K, V>'s V as a type

Time:09-15

I'm trying to make the following function generic i.e. if a HashMap<&str, u32> is passed, then instead of n < u8::BITS, it should work like n < u32::BITS.

fn to_flag<'a>(
  attack: &'a str,
  attack_to_flag: &mut HashMap<&'a str, u8>,
) -> Result<T, Box<dyn Error>> {
  let n = attack_to_flag.len() as u32;
  match n < u8::BITS {
    true => Ok(*attack_to_flag.entry(attack).or_insert(1 << n)),
    false => Err(Box::<dyn Error>::from(
      "More than 8 attacks; u8 insufficient.",
    )),
  }
}

In C I used to do map.mapped_type to get the type. Another approach I tried

fn to_flag<'a, T>(
  attack: &'a str,
  attack_to_flag: &mut HashMap<&'a str, T>,
) -> Result<T, Box<dyn Error>> {
  let n = attack_to_flag.len() as u32;
  match n < T::BITS {
    true => Ok(*attack_to_flag.entry(attack).or_insert(1 << n)),
    false => Err(Box::<dyn Error>::from(
      "More than 8 attacks; u8 insufficient.",
    )),
  }
}

But the compiler, understandably, complains

error[E0599]: no associated item named `BITS` found for type parameter `T` in the current scope
  --> src\main.rs:41:16
   |
41 |   match n < T::BITS {
   |                ^^^^ associated item not found in `T`

error[E0308]: mismatched types
  --> src\main.rs:42:56
   |
36 | fn to_flag<'a, T>(
   |                - this type parameter
...
42 |     true => Ok(*attack_to_flag.entry(attack).or_insert(1 << n)),
   |                                              --------- ^^^^^^ expected type parameter `T`, found integer
   |                                              |
   |                                              arguments to this function are incorrect
   |
   = note: expected type parameter `T`
                        found type `{integer}`

I was unable to find an Integer trait. What are my options?

CodePudding user response:

The num-traits crate contains a lot of numeric traits, including for example PrimInt. However, it doesn't contains a trait for BITS.

You can easily create a trait that has a BITS constant:

trait Bits {
  const BITS: usize;
}
macro_rules! impl_bits {
  ( $($ty:ident)* ) => {
    $(
      impl Bits for $ty {
        const BITS: usize = Self::BITS as usize;
      }
    )*
  };
}
impl_bits!(u8 u16 u32 u64 u128);

Then

fn to_flag<'a, T: Bits   PrimInt>(
  attack: &'a str,
  attack_to_flag: &mut HashMap<&'a str, T>,
) -> Result<T, Box<dyn Error>> {
  let n = attack_to_flag.len();
  let mask = T::one() << n;
  match n < T::BITS {
    true => Ok(*attack_to_flag.entry(attack).or_insert(mask)),
    false => Err(Box::<dyn Error>::from(
      "More than 8 attacks; u8 insufficient.",
    )),
  }
}

CodePudding user response:

One question you ask is how to get the value type of a HashMap.

Usually, in Rust, this is done by type destructuring through templates:

fn do_something<K, V>(_m: &HashMap<K, V>) {
    // `V` is the value type, do something with it if desired
}

Using this mechanism, we could write a function that returns the size of the value type:

use std::collections::HashMap;

fn get_size_of_value<K, V>(_m: &HashMap<K, V>) -> usize {
    std::mem::size_of::<V>()
}

fn main() {
    let m = HashMap::<String, u16>::new();
    println!("Size of value type: {} bytes", get_size_of_value(&m));
}
Size of value type: 2 bytes

As this funciton doesn't even depend on the input value, only its type, there is a very high chance that this method will be evaluated at compile time and inlined, giving you zero performance overhead.

Of course, this only works because all types can be passed into std::mem::size_of. If you desire to do something more type-specific, you might have to specify trait restrictions to your V template type.

Hopefully this gives you an idea of how to aproach these types of problems.

Note that @Chayim's answer is basically the same thing. Just instead of using a separate function, he used the to_flag function itself to destructure the type via a template; and instead of using size_of, he used a custom BITS wrapper. But the underlying principle is identical.

  • Related