Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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.

like image 495
russoue Avatar asked May 10 '18 05:05

russoue


People also ask

Can primitive types be used in generics?

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.

Why don t java generics support primitive types?

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.

What is primitive rust?

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.


1 Answers

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

like image 145
U007D Avatar answered Oct 08 '22 20:10

U007D