I am trying to create a parser in Rust that reads an output file of our simulations, finds the correct block given the variable name, and returns the content of such block, which can be a single int or a vector of ints or floats, as determined by the given dimensions.
However, I cannot find a way to use the generic type T as a way to use the same function for i32 and f64 numbers.
I do not understand how to work with generics in this way.
I have started today with Rust, so please explain the solution to me like I am 5 years old.
A simplified version of my understanding problem is the following:
fn parse_b2f<T>(dims: Vec<i32>) -> Result<Vec<T>, String> {
let nentries = dims.iter().fold(1, |res, a| res*a);
Ok(vec![nentries])
}
fn main() {
let dims = vec![3720, 9];
let data = parse_b2f::<i32>(dims);
}
but I get expected type paramter T, found i32, pointing at nentries in the last line of the parse_b2f function. If I have provided i32 in the function call in main, why is T still treated as a generic, and not as i32?
If I change T to i32, the function works, but the idea is to let the user pass the type (i32 for an array of indices, f64 for an array of experimental data, etc.), instead of having to write two functions doing exactly the same logic.
I was hopping to be able to do something like vec![nentries as T], since with i32 and f64 separately it works, but not with T.
I have also thought about using an enum:
enum Numbers {
I32(i32),
F64(f64),
}
instead of T with
fn parse_b2f<Numbers>(dims: Vec<i32>) -> Result<Vec<Numbers>, String> {...}
let data = parse_b2f::Numbers::I32(dims);
but I do not really know how to work with that enum in this case.
This would work in C++ because types aren't checked until instantiation. In Rust, the generic function must stand alone. The parse_b2f function doesn't constrain T so it must be callable for any type T at all. This is why you get the error. You can remove main()'s whole body and you will still get this error.
All you need to do is suitably constrain T.
In this simple case, you need is to say "T must be a type that supports multiplication, where the result is T." You do this by constraining it on std::ops::Mul<Output = T>.
Additionally, you need a way to obtain the initial value of 1, which you can do with a helper trait. (There is such a trait in the num-traits crate.)
Finally, you either need to make T constrained on Copy, or use .into_iter() instead of .iter() because .iter() produces references (and the referent must be copied to supply to the multiplication operation) but .into_iter() produces values by consuming the input vector.
Here's a working example:
use std::ops::Mul;
use num_traits::One;
fn parse_b2f<T: One + Mul<Output = T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
let nentries = dims.into_iter().fold(T::one(), |res, a| res*a);
Ok(vec![nentries])
}
Consider also changing the function to accept anything that can be converted to an Iterator of T (we say this IntoIterator<Item = T>). This allows passing a Vec<T> but also allows passing a non-owning iterator:
use std::ops::Mul;
use num_traits::One;
fn parse_b2f<T: One + Mul<Output=T>>(dims: impl IntoIterator<Item = T>)
-> Result<Vec<T>, String>
{
let nentries = dims.into_iter().fold(T::one(), |res, a| res*a);
Ok(vec![nentries])
}
The answer of @cdhowie explains well the reason, but instead of using the traits they show (that also includes an external crate) you can realize that you are doing the same as Iterator::product(), and just use its trait - Product. You can either use T: Product<T> and into_iter(), or T: Product<&T> and iter():
fn parse_b2f<T: std::iter::Product<T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
let nentries = dims.into_iter().product();
Ok(vec![nentries])
}
// Or
fn parse_b2f<T: for<'a> std::iter::Product<&'a T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
let nentries = dims.iter().product();
Ok(vec![nentries])
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With