In the previous chapters, we introduced the philosophy of Rust and set up our development environment. Now it’s time to interact with the user — collecting data that will power our BMI calculator.
But here’s the truth: we can’t trust user input. It might be letters instead of numbers, strange values like 0 or negatives, or even an empty line. Rust forces us to treat every input as potentially wrong, and this is where its philosophy of rigor shines.
We’ve created our Rust project and confirmed that everything works. The next step is to let the user enter data. In our case, we want to collect weight and height in order to calculate BMI.
But here’s the catch: when we ask for input, we can’t trust it.
- The user might type letters instead of numbers.
- They might enter strange values, like 0 or negative numbers.
- They might just press Enter without writing anything.
In Rust, whenever we deal with uncertain data, we must handle errors in a clear and safe way. This chapter is about learning the first steps with input, parsing, and validation.
Reading input from the keyboard
Rust uses the std::io library to read from the keyboard. Let’s see the simplest example:
use std::io;
fn main() {
let mut input = String::new(); // create an empty string
println!("Enter your weight in kg:");
io::stdin() // take the "standard input"
.read_line(&mut input) // read a line and save it into 'input'
.expect("Failed to read input");
println!("You entered: {}", input);
}
What happens here?
- We create a mutable variable (
mut input) that will store what the user types. io::stdin().read_line(&mut input)→ reads until the Enter key.expect("Failed...")stops the program with a message if something goes wrong.
Converting the input into a number
The value we read is always a string. But to calculate BMI we need numbers. We use .trim().parse():
let weight: f32 = input.trim().parse().expect("You must enter a number!");
.trim()→ removes spaces and line breaks..parse::<f32>()→ tries to convert to a decimal number (f32 = floating point)..expect()→ if it fails, the program stops with the error.
Not ideal yet, because stopping the program isn’t very user-friendly. But it shows us what happens if you type letters instead of numbers.
Handling errors gracefully
In Rust, we can use match to manage both success and error cases:
let weight: f32 = match input.trim().parse() {
Ok(num) => num, // success: return the number
Err(_) => {
println!("Error: please enter a valid number.");
return; // exit the program
}
};
This way the program doesn’t crash but shows a clear message.
Creating a reusable function
To avoid writing the same code twice (for weight and height), we can create a function:
fn read_number(prompt: &str) -> f32 {
loop {
let mut input = String::new();
println!("{}", prompt);
std::io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
match input.trim().parse::<f32>() {
Ok(num) if num > 0.0 => return num, // valid number
Ok(_) => println!("The value must be greater than zero."),
Err(_) => println!("Invalid input, please try again."),
}
}
}
And in main:
fn main() {
let weight = read_number("Enter your weight in kg:");
let height = read_number("Enter your height in meters:");
println!("Weight: {}, Height: {}", weight, height);
}
What we learn here:
- We use an infinite loop (
loop { ... }) that only ends when the user enters valid data. matchhandles three cases:- Valid number (> 0).
- Valid number but not realistic (0 or negative).
- Conversion error (letters instead of numbers).
End-of-chapter reflection
Many languages treat input as “probably fine” and deal with errors later. Rust is different:
- Every input is treated as potentially wrong.
- The compiler forces us to think about errors, and that makes the program safer.
- Even a simple BMI calculator becomes an exercise in care and respect for the user.
The result? A small program that never crashes and patiently guides the user until they do the right thing. That’s our first real “wow” moment in Rust.
