I am trying to write a generic function which will try to convert a string to a number type like i32
, f64
etc. If the string is not convertible then it will return 0
. I am looking for an appropriate trait bound to use in my generic function below:
use std::str::FromStr;
fn get_num_from_str<T: FromStr>(maybe_num_str: &String) -> T {
let maybe_num = T::from_str(maybe_num_str.as_str());
if maybe_num.is_ok() {
return maybe_num.unwrap();
}
0 as T
}
fn main() {
let num_str = String::from("12");
println!("Converted to i32: {}", get_num_from_str::<i32>(&num_str));
}
Playground Link
I found that Rust had a Primitive
trait before which was removed. Is there something else now that can be used instead?
I have found a workaround:
use std::str::FromStr;
fn get_num_from_str<T: Default + FromStr>(maybe_num_str: &String) -> T {
let maybe_num = T::from_str(maybe_num_str.as_str());
maybe_num.unwrap_or(Default::default())
}
Playground Link
This, as the trait bound suggests, should work for anything which has implementations for both Default
and FromStr
and I should rename the function to reflect that, but it would still be nice to know if there is any trait for the primitive number types only which I can use to make sure this function cannot be used for anything other than the number types.
Using generics, primitive types can not be passed as type parameters. In the example given below, if we pass int primitive type to box class, then compiler will complain. To mitigate the same, we need to pass the Integer object instead of int primitive type.
So, anything that is used as generics has to be convertable to Object (in this example get(0) returns an Object ), and the primitive types aren't. So they can't be used in generics.
The Rust language has a number of types that are considered 'primitive'. This means that they're built-in to the language. Rust is structured in such a way that the standard library also provides a number of useful types built on top of these ones, as well, but these are the most primitive.
The Num
trait defined in the num crate should meet your needs, but what you are trying to do is not idiomatic in Rust, so I'd like to offer two suggestions:
Instead, consider using the Result<T, E>
type if your function can fail to return a result for more than one reason or the Option<T>
type if there is exactly one reason why your function might not be able to return a result.
In your case, you might want to use a Result<T, E>
so your function can communicate why the conversion failed--did the string contain invalid numeric characters? Was the value out of range for the type that was requested? Callers to your function may need to know to be able to best deal with the situation.
std::string::String::parse()
or the str::parse()
methods. For reasons cited above, these methods do indeed return a Result
.With the above two pieces of information, your code can now be rewritten more robustly as follows (using Rust 1.26+ to simplify error handling):
type Result<T> = std::result::Result<T, Box<std::error::Error>>;
fn main() -> Result<()> {
let num_str = String::from("12");
println!("Converted to i32: {}", num_str.parse::<i32>()?);
Ok(())
}
playground example
In the event there is a problem, notice how nicely errors are reported:
type Result<T> = std::result::Result<T, Box<std::error::Error>>;
fn main() -> Result<()> {
let num_str = "-1";
println!("Invalid u32 conversion: {}", num_str.parse::<u32>()?);
Ok(())
}
playground example
This prints Error: ParseIntError { kind: InvalidDigit }
to the console and returns the value std::libc::EXIT_FAILURE
to the calling process (signifying the program exited with an error) all without any value overloading or "magic numbers" required.
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