9

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
russoue
  • 5,180
  • 5
  • 27
  • 29
  • I think the `Default` trait might help me to return `0`. – russoue May 10 '18 at 05:43
  • 1
    You are probably interested in one of the traits in the `Num` crate; such functionality is not provided in the `std`. – ljedrz May 10 '18 at 06:07
  • 1
    *`if maybe_num.is_ok() { return maybe_num.unwrap(); }`* — please never do this, this is an antipattern. Use pattern matching to avoid the `unwrap`: `if let Some(v) = maybe_num { return v; }`. – Shepmaster May 10 '18 at 14:46

2 Answers2

11

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:

  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<dyn std::error::Error>>;

fn main() -> Result<()> {
    let num = "42".parse::<i32>()?;  // also works with `String`
    
    println!("Converted to i32: {}", num);
    
    Ok(())
}

playground example

In the event there is a problem, notice how nicely errors are reported:

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

fn main() -> Result<()> {
    let num = "-1".parse::<u32>()?;
    
    println!("Converted to u32: {}", num);
    
    Ok(())
}

playground example

This outputs 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.

U007D
  • 5,772
  • 2
  • 38
  • 44
4

No, there is no such trait. Why? Because Rust doesn't need to care about distinguishing "primitives" from "non-primitives" as much as most other languages. Realistically, why should it?

Additionally, note that arrays and raw pointers are also primitives; do you really wish to include those?

which I can use to make sure this function cannot be used for anything other than the number types.

Why would you ever want to be artificially restricted on what types you can use? Why shouldn't your function work with any type that meets the basic criteria it needs? Who are you trying to protect yourself from?

Your example can be shortened though:

fn get_num_from_str<T: Default + FromStr>(s: &str) -> T {
    s.parse().unwrap_or_default()
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366