Reputation: 970
I have the following function:
use std::collections::HashMap;
use num::traits::Unsigned;
fn test<T: Unsigned>(strings: Vec<String>) -> HashMap<String, T>{
let mut string_map: HashMap<String, T> = HashMap::new();
for (i, string) in strings.iter().enumerate() {
string_map.insert(string.clone(), i);
}
string_map
}
that causes the following compilation error:
error[E0308]: mismatched types
--> src/main.rs
|
| string_map.insert(string.clone(), i);
| ^ expected type parameter, found usize
|
= note: expected type `T`
found type `usize`
= help: type parameters must be constrained to match other types
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
Although the Unsigned
trait is implemented only for unsigned integer types, the compiler is not able to recognize this.
How can I cast i
to T
, since i as T
is not working either?
Upvotes: 2
Views: 1264
Reputation: 26767
You need a concrete type, you can hide it but in your example it's always be usize so you can do:
use num::traits::Unsigned;
use std::collections::HashMap;
fn test(strings: Vec<String>) -> HashMap<String, impl Unsigned> {
strings
.into_iter()
.enumerate()
.map(|(i, s)| (s, i))
.collect()
}
Upvotes: 2
Reputation: 28065
Another way to approach this problem, instead of converting from usize
to T
, is to not use enumerate
and instead bound T
such that it can be the counter.
use num::{Integer, Unsigned};
use std::collections::HashMap;
fn test<T: Clone + Unsigned + Integer>(strings: Vec<String>) -> HashMap<String, T> {
let mut string_map: HashMap<String, T> = HashMap::new();
let mut i = T::zero();
for string in strings {
string_map.insert(string, i.clone());
i = i + T::one();
}
string_map
}
T
has two new bounds:
Clone
is necessary to be able to insert a clone into the map and keep iterating with the counter;Integer
allows you to use T::zero()
, T::one()
and addition.Be aware the behavior of this solution depends on the overflow behavior of T
, while justinas's answer will always panic if i
goes out of range (or can be rewritten to return a Result
, which may be more desirable).
Upvotes: 1
Reputation: 6867
as
only does primitive casts between built-in numerical types, but Unsigned
can be implemented for your own types, etc. That's why as
does not work.
You can utilize the TryFrom
trait:
use num::traits::Unsigned;
use std::collections::HashMap;
use std::convert::TryFrom;
fn test<T>(strings: Vec<String>) -> HashMap<String, T>
where
T: Unsigned + TryFrom<usize>,
<T as std::convert::TryFrom<usize>>::Error: Debug, // only for unwrap()
{
let mut string_map: HashMap<String, T> = HashMap::new();
for (i, string) in strings.iter().enumerate() {
string_map.insert(string.clone(), T::try_from(i).unwrap());
}
string_map
}
fn main() {
let result: HashMap<_, u16> = test(vec!["abc".into(), "def".into()]);
}
In theory, you could use From
instead of TryFrom
, but in practice, most integer types, e.g. u8
, u16
, u32
are not guaranteed to fit usize
. So TryFrom
and error handling is needed.
Upvotes: 3