HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

From a string of 3 numbers to 3 integers

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
integersfromstringnumbers

Problem

I'm solving some puzzles to learn Rust. A problem I faced is to turn a string of 3 integers into 3 integers. I succeeded with the following example:

fn main() {
    let mut it = "   455   123   42  ".split_whitespace();
    let (a, b, c) = match (it.next(), it.next(), it.next()) {
        (Some(a), Some(b), Some(c)) => {
            (a.parse::().unwrap(),
             b.parse::().unwrap(),
             c.parse::().unwrap())
        }
        _ => panic!("Oops"),
    };

    println!("{:?}", (a, b, c));
}


However, this code appears to have bad quality:

  • bad error handling



  • does not scale (works only for 3) with N integers.



How can I improve this code?

Solution

I'd write the code as

fn main() {
    let input = "   455   123   42  ";

    let numbers: Result, _> = input
        .split_whitespace()
        .map(str::parse)
        .collect();

    println!("{:?}", numbers);
}


As mentioned in a comment, iterators are very idiomatic in Rust. The Iterator trait is the main documentation to check out. Once you have an iterator, such as by calling split_whitespace, you can do powerful things.

In this case, we can map a closure or a function across each element in the iterator, converting it from one type to another. Although we could have used map(|the_string| the_string.parse()), I prefer the shorter form of syntax when I can use it.

We converted each element using str::parse, which returns a Result, one of Rust's main error handling types. At this point, each element from the iterator will be a Result, but we don't know what type it is yet! parse is powerful in that you can parse to many types, but that power comes at a cost — you have to specify what type you want to parse to. In the original code, the turbofish operator was used (::<>), but I'm not the biggest fan of the fish.

After all this code, we haven't really done anything yet. Rust iterators don't do anything until you pull values from them via next. We could call next over and over ourselves, but in this case I'd like a collection of all the values. That's where collect comes into play.

Similar to str::parse, collect allows the caller to specify what type should be collected into. This allows us to collect into a Vec or a HashSet or many other types. In this case, we can collect into Result. If any of the iterator values were invalid, the entire collection will be invalid with the first found error. Otherwise, we will construct a Vec. Here is where we finally specify what type to parse into as well: an i32. We leave the error type up to the compiler by using the underscore _; once we have specified what kind of type to parse to, we also specify the error type.

To wrap it all up, we print the value to the user using the Debug formatter. This will show us if the Result was an Ok with the entire vector of values or an Err along with the error itself.


bad error handling

I'd point out that your original code has more error handling compared to many other languages, just because of Rust. For example, each call to unwrap or panic is error handling that other languages might have deferred to runtime (or never!). You handled the cases of the iterator being too short (although see below) as well as when the number failed to parse.

Your error reporting mechanism is fairly basic: the program ends when an error is encountered. There's nothing wrong with that though. It's far better than continuing on when you don't know how to!

As a side note, it's undefined behavior what the result of calling Iterator::next after it returns None the first time. The iterator is free to return nonsense values. If you need to call an next multiple times without checking the value in between each call, you should fuse the iterator, ensuring it will always return None after the first None.

Code Snippets

fn main() {
    let input = "   455   123   42  ";

    let numbers: Result<Vec<i32>, _> = input
        .split_whitespace()
        .map(str::parse)
        .collect();

    println!("{:?}", numbers);
}

Context

StackExchange Code Review Q#149241, answer score: 4

Revisions (0)

No revisions yet.