Padix Key
Padix Key

Reputation: 970

How to convert a concrete integer into a generic integer?

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

Answers (3)

Stargateur
Stargateur

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

trent
trent

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

justinas
justinas

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

Related Questions