Idiomatic Rust?

Hello. I'm new to Rust. I'm reading the Rust Programming Language (I bought two copies :grin:) and Programming Rust and using codewars.com to try and exercise what I've learned thus far. So there is a lot of guess work and looking things up on doc.rust-lang.org because I don't have a real sense of idiomatic Rust yet. In order to speed up the process of learning idiomatic Rust I'd like to post my codewars solutions here and have it reviewed.


// Given a string containing space separated numbers
// return a string with the highest and lowest number
fn high_and_low(src: &str) -> String {
    let mut v: Vec<i32> = Vec::new();
    for c in src.split_ascii_whitespace() {
        v.push(c.parse().unwrap());
    }
    v.sort();
    let mut rs = String::new();
    rs.push_str(&v[v.len() - 1].to_string());
    rs.push(' ');
    rs.push_str(&v[0].to_string());
    rs
}
1 Like

This looks fine, for the most part!

A few little tweaks you could make:

  • Instead of explicitly creating a Vec and then pushing things in via a for loop, you could use a map and a collect on the iterator returned from split_ascii_whitespace.
  • Rather than manually pushing stuff into a String, you could use the format! macro.
  • Rather than indexing, you could use the first and last methods on Vec (although bear in mind that, since the Vec could be empty, these return an Option you'll have to unwrap or handle).

Aside from what has already been mentioned, instead of unwrapping when trying to parse the string as a number, you should just skip the garbage value unless the requirement is to crash the program at the slightest ill-formed user input.

1 Like

@17cupsofcoffee Most of what was said required looking stuff up so here is what I've come up with.

fn high_and_low(numbers: &str) -> String {
    let mut v: Vec<i32> = numbers.split_ascii_whitespace()
                                 .map(|c| c.parse().unwrap())
                                 .collect();
    v.sort();
    format!("{} {}", v.last().unwrap().to_string(), v.first().unwrap().to_string())
}

I don't understand what you mean by this. When I first read it I understood it to mean "remove the call to unwrap" but when I did that the compiler got :angry:

1 Like

That looks like what I had in mind :slight_smile: You don't need the to_string calls on the numbers, though - i32 implements Display, so you can use it in format strings without converting it first.

I think they were suggesting skipping invalid values - you could do that by replacing your map with this:

// .ok() turns a Result into an Option: Ok(T) becomes Some(T), Err(E) becomes None
// filter_map filters out items that return None
.filter_map(|c| c.parse().ok())

This does throw away any parsing errors, though.

Replacing unwrap with ok resulted in:

a value of type `std::vec::Vec<i32>` cannot be built from an iterator over elements of type `std::option::Option<_>`

The other changes worked though so I'm happy. Thank you so much for turning my butt ugly 18+ years of programming in other languages code into something beautiful. :heart_eyes:

1 Like

I think you forgot to replace map with filter_map - doing a map over the oks will just create an iterator of Options, rather than filtering out the Nones.

map transforms values, filter filters out values, and filter_map lets you do them both at the same time :slight_smile:

That did the trick! Thanks and thank you @Phlopsi and forgive my ignorance; I'm still a n00b :grin:.

2 Likes

Note that you can skip two .to_string() calls within the format! macro.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.