I posted a thread similar to this a few days ago, but my question this morning is enough different that I decided to do a second one. I'm still trying hard to learn how to use generics in the functions I have in my personal library. The function below simply checks to see if a string is numeric and returns a bool. Here's a copy that works just fine:
fn main() {
let num = "34.5";
if chk4num(&num) {
println!("Yes, it is a number. ");
return;
} else {
println!("No, it's not a number!");
};
}
pub fn chk4num(strng: &str) -> bool {
match strng.trim().parse::<f32>() {
Ok(_) => true,
Err(_) => {
return false;
}
}
}
This works great as long as I annotate the parse() method with the expected type. However, it's kind of annoying to have to create a version of this function for every possible type of number, so using generics makes sense. It's just that every attempt I make at using a generic type in my code generates compiler errors. Here's the simplest of those attempts:
4 | pub fn chk4num(strng: &str) -> bool {
| - help: you might be missing a type parameter: `<T>`
15 | match strng.trim().parse::<T>() {
| ^ not found in this scope
I could go on listing all the failed modifications I've tried, but it might be simpler if someone could just walk me through converting this function to use generics. Thanks.
You can't use generic parameters that aren't declared or implied by the function signature or impl block (when there is one). So a first step could be adding a parameter:
error[E0277]: the trait bound `T: FromStr` is not satisfied
--> src/lib.rs:2:24
|
2 | match strng.trim().parse::<T>() {
| ^^^^^ the trait `FromStr` is not implemented for `T`
|
help: consider restricting type parameter `T`
|
1 | pub fn chk4num<T: std::str::FromStr>(strng: &str) -> bool {
| +++++++++++++++++++
Having just a type parameter isn't useful; you also have to use trait bounds to declare what capabilities you need to make use of. It's a two-way contract: the function body can only make use of bounds they've declared, and callers can only call if they meet the bounds. This way there are no run-time failures due to some type not implementing the bound.
The bound suggested in the error would accept any type that implements FromStr, and not just number. There's no std trait for "any numeric type", but there is one in num. That trait has a distinct parsing function you could use instead, but you have to specify the radix. Or you could bound on both Num and FromStr.
So that technically addresses your question. However, I'm not sure where you're headed with making the function generic. Being generic over numbers can be a pain, so whatever you're attempting may not be the most pleasant experience. The capabilities of numeric types are spread across a large number of traits, and numeric types aren't nearly as unified as many people intuit (perhaps due to decades of programming languages silently doing potentially lossy and/or unspecified conversions when you mix them).
The traits in the num crate can help, but it still tends to be messy. So it's somewhat unfortunate that a lot of people's first stab at generics involves numbers.
Well, to avoid having to create a new function for every number type. Using either your example or @linhns solution above, I won't have to create a chk4_u32() or chk4_f64() or ... a separate function for each number type.
Besides that, I'm trying to learn more about generics. Converting the functions I've been using to use generics seems to be a good learning activity that should be useful too.
Yes, I'm beginning to realize that. Seems like a weakness in the Rust language, but I think that is the price we pay for the safety offered by Rust's pickiness about being consistent with our data types.
What I'm getting out of this is that I have to be sure to annotate the type whenever I call the function. Otherwise the compiler won't be able to infer the correct type.
I understand this is a convenience function, probably made for learning, but you can write this like "value".parse::<i32>().is_ok(), which is so short there's no need to put it into a function.
@quinedot & @linhns So each of you suggested a good solution to my issue. Looking at the two, which would you say is the better solution?
I have one more function involving numbers -- a bubble sort routine that I wrote as a learning exercise -- that I want to convert to using generics. I'm going to try applying what I've learned so far and see how it goes. When I get stuck I'll do a new post and maybe you can help me with that, too.
Thanks, guys, for your help. I really appreciate it.
For the specific function you mentioned, there's no real need to limit it to numbers. When you call that function, you've already chosen a specific type in your code -- if it needs to be a number, it will be a number. That would be @linhns solution.
(But there's no real need to check first and then convert either -- just try to convert and if str::parse::<Number> returns Err, they didn't give you a number; if it returns Ok, store the number so you don't have to convert it again later.)
I definitely recommend using the num crate traits for that. You may have a lot of bounds because numbers, but it'll still be a learning experience. And like you said you can always post again if you get stuck.
Personally, I wouldn't use num for this. A bubble sort (or any other comparison-based sort) should only need to bound on Ord¹, which will let you compare two items of the same type. There's no good reason to prevent your function from sorting Strings, for example, because the implementation is exactly the same.
fn bubble_sort<T:Ord>(items: &mut [T]) { ... }
¹ This unfortunately doesn't cover floating-point numbers because NaN != NaN. If you're careful, I think it's possible to write a correct bubble sort to cover this case that only needs PartialOrd. It's been a long time since my algorithms class, though.