Split string and convert to integer

I'm starting with Rust. I have the following code, which works well, but I see it quite ugly. How can I improve this?

What I try to get is the value after: as an integer
"user:2"
in this case the number 2

fn main() {
    let port_data: &str = "user:2";
    let data: Vec<&str> = port_data.split(":").collect();
    println!("data split: {:?}", data);
    let card = match data.get(1) {
        Some(x) => x,
        _ => "0"
    };
    let card: i32 = card.parse::<i32>().unwrap_or(0);
    println!("card: {}", card);
}

Thanks
Jose

a generic solution could be:

use std::str::FromStr;

fn parse_data<T: FromStr>(s: &str, separator: char) -> Option<T> {
    match s.find(separator) {
        None => None,
        Some(index) => match T::from_str(&s[index + 1..]) {
            Ok(l) => Some(l),
            _ => None,
        },
    }
}

fn main() {
    let port_data = "user:2";
    if let Some(data) = parse_data::<i32>(port_data, ':') {
        println!("data: {:?}", data);
    }
}

1 Like

I would use the ? operator and move the parsing into its own function. That way you can avoid all the match or unwrap()s.

fn get_number(text: &str) -> Option<u32> {
    // create an iterator over the pieces in our input string
    let mut words = text.split(":");

    // skip past the first word, returning None if there wasn't one
    let _user = words.next()?;

    // then save the second word to a variable
    let number = words.next()?;

    // finally, parse the number and because we don't care about the  
    // error we can use ok() to convert the Result<u32, Error> to an 
    // Option<u32>
    number.parse().ok()
}

(playground)

4 Likes

Another way using cascading functions:

fn get_number(s: &str) -> Option<u32> 
{
    s.find(':')
     .map(|i| s[i+1..].parse().ok())
     .flatten()
}

or using an iterator and ? as suggested by @Michael-F-Bryan

fn get_number(s: &str) -> Option<u32>
{
    s.split(':')
     .skip(1)    // skip "user"
     .next()?    // the ? returns None if necessary
     .parse()    // result is Result<u32,...>
     .ok()       // transforms a Result into an Option
}
2 Likes

How many answers and so fast!

Thank you. Now I'm going to review

1 Like

You could use split_once to get a pair of strings:

fn main() {
    let port_data: &str = "user:2";
    let value = port_data.split_once(":").map(|(_, v)| v).unwrap_or("0");
    let card = value.parse::<i32>().unwrap_or(0);
    println!("card: {}", card);
}

Even simpler:

3 Likes

Thanks for the answers again

I see that in all the response you used find and not split.
Actually my string comes from the reading of the serial port and it will be something similar to "A:U:V:W:X:Y:Z"
And I must get each of the values: A, U, V, etc with the correct data type according to the case.

Jose

Well then you can map over the iterator returned by split.

another solution with filter_map:

fn main() {
    let port_data = "A:10:V:120:X:37:Z:73";
    let words: Vec<_> = port_data
        .split(":")
        .filter_map(|word| i32::from_str(word).ok())
        .collect();
    println!("words: {:?}", words);
}