I'm trying to make the following function generic. It currently uses u8::BITS
but I want if a HashMap<&str, u32>
is passed then use u32::BITS
instead.
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.