How do I ask for user input until the input is a number?

Viewed 64

I have this simple code to get the BMI (starting to learn Rust). It works fine.

use std::io;

fn main() {
    println!("***Rust BMI***");

    let mut w: f32 = 0.0;
    let mut weight = String::new();
    println!("Inform your weight: ");
    io::stdin()
        .read_line(&mut weight)
        .expect("Failed to read weight");
    w = weight.trim().parse().expect("Not a number");
    println!("Your weight is {}kg", w);

    let mut h: f32 = 0.0;
    let mut height = String::new();
    println!("Inform your height: ");
    io::stdin()
        .read_line(&mut height)
        .expect("Failed to read height");
    h = height.trim().parse().expect("Not a valid number");
    println!("Your height is {}m", h);
    println!("Your bmi is {}", bmi(w, h));
    println!("You are: {}", bmi_res(w, h));
}

fn bmi(weight: f32, height: f32) -> f32 {
    weight / (height * height)
}

fn bmi_res(weight: f32, height: f32) -> &'static str {
    let index = weight as f32 / height.powi(2);
    match index {
        index if index <= 18.5 => "Underweight",
        index if index <= 25.0 => "Normal",
        index if index <= 30.0 => "Overweight",
        _ => "Obese",
     }
}

My question is: how would I check only for numbers (float specially)? If the user informs anything different from that, the program crashes. I’ve tried if, but to no avail. Also tried match, but couldn't validate the input. I don’t know and could not find anything “is_float()” like method.

What I mean is: if the user informs anything different from number, he's supposed to be redirected to the same question, "Inform your weight/height", and not have the program crashed.

Here is the repo.

2 Answers

expect unwraps a Result and returns its contents if it is good, and panics if the Result contains an error value. You need to handle the error case gracefully with pattern matching (or more precisely in your case with if let):

let w = loop {
    println!("Inform your weight: ");
    io::stdin()
        .read_line(&mut weight)
        .expect("Failed to read weight");
    if let Ok (w) = weight.trim().parse::<f32>() {
        break w;
    } else {
        println!("Please enter a valid floating-point number!");
    }
}

You .expect("") the Result of the .parse(), which means you tell rust to panic with a custom error message, if the 'Result' is of type 'Err'. If you don't want rust to panic, you'll have to match on the return value with a if let Ok(val) = str.trim().parse::<f32>() {}. Thus, rewrite every expect with the if let pattern:

use std::io;

fn main() {
    println!("***Rust BMI***");

    let mut w: f32 = 0.0;

    loop {
        let mut weight = String::new();
        println!("Inform your weight: ");
        io::stdin()
            .read_line(&mut weight)
            .expect("Failed to read weight");
        if let Ok(val) = weight.trim().parse::<f32>() {
            w = val;
            println!("Your weight is {}kg", w);
            break;
        } else {
            println!("Please enter a valid number.");
        }
    }

    let mut h: f32 = 0.0;
    loop {
        let mut height = String::new();
        println!("Inform your height: ");
        io::stdin()
            .read_line(&mut height)
            .expect("Failed to read height");
        if let Ok(val) = height.trim().parse::<f32>() {
            h = val;
            println!("Your height is {}kg", w);
            break;
        } else {
            println!("Please enter a valid number.");
        }
    }

    println!("Your height is {}m", h);
    println!("Your bmi is {}", bmi(w, h));
    println!("You are: {}", bmi_res(w, h));
}

fn bmi(weight: f32, height: f32) -> f32 {
    weight / (height * height)
}

fn bmi_res(weight: f32, height: f32) -> &'static str {
    let index = weight as f32 / height.powi(2);
    match index {
        index if index <= 18.5 => "Underweight",
        index if index <= 25.0 => "Normal",
        index if index <= 30.0 => "Overweight",
        _ => "Obese",
    }
}
Related