Is there a trait for only primitive types that I can use in a generic function?

  • A+
Category:Languages

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.


The Num trait defined in the num crate might meet your needs, but what you are trying to do is not idiomatic in Rust, so I'd like to offer two suggestions:

  1. In some languages like C, for example, it is traditional to overload particular values like 0 or -1 with special meanings, but this has been shown to be a source of confusion and even hard bugs.

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.

  1. The Rust standard library already includes the functionality you are looking for (conversion of strings to a value via a generic implementation) in the form of the 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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: