Step 7 of 7
Handling bad input with match
.expect is fine when bad input means something is badly wrong. But people type the wrong thing all the time: abc, an empty line, 4.5 where a whole number was expected. A good program reacts to that instead of crashing.
parse returns a Result: either Ok(value), holding the number, or Err(error), describing what went wrong. .expect takes the value out of Ok and crashes on Err. To decide for yourself what happens, use match:
fn main() {
let inputs = ["42", "-7", "abc", "", "99999999999"];
for text in inputs {
match text.parse::<i32>() {
Ok(n) => println!("{text:?} -> number {n}"),
Err(e) => println!("{text:?} -> error: {e}"),
}
}
}
"42" -> number 42
"-7" -> number -7
"abc" -> error: invalid digit found in string
"" -> error: cannot parse integer from empty string
"99999999999" -> error: number too large to fit in target type
match value { pattern => code, ... }compares the value with each pattern in turn and runs the first arm that fits. Module 14 coversmatchin full; here you only need these two patterns.Ok(n)matches a successful parse and names the numberninside that arm.Err(e)matches a failure;eis the error, which prints a readable reason with{}. WriteErr(_)when you don't need it.- There's no variable annotation here, so the turbofish
parse::<i32>()says which type to parse into.
The compiler checks that you handle both cases. Leave out the Err arm and the program doesn't compile:
fn main() {
let text = "42";
match text.parse::<i32>() {
Ok(n) => println!("{n}"),
}
}
error[E0004]: non-exhaustive patterns: `Err(_)` not covered
match produces a value
Like if, match is an expression. Each arm can give a value, and an arm can also jump away with continue, break or return:
fn main() {
let lines = ["5", "ten", "7", ""];
let mut total = 0;
for text in lines {
let qty: i32 = match text.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("skipping {text:?}");
continue;
}
};
total += qty;
}
println!("total: {total}");
}
skipping "ten"
skipping ""
total: 12
Ok(n) => n makes the whole match evaluate to the number, which lands in qty. The Err arm never produces a value: it prints a note and moves on to the next line with continue. This "use it or skip it" shape is very common.
Asking again
For interactive programs, combine match with loop and break value (from the control module) to ask until the answer makes sense:
use std::io;
fn main() {
let age: u32 = loop {
println!("How old are you?");
let mut line = String::new();
let bytes = io::stdin().read_line(&mut line).expect("failed to read a line");
if bytes == 0 {
println!("No answer, giving up.");
return;
}
match line.trim().parse() {
Ok(n) => break n,
Err(_) => println!("'{}' isn't a whole number, try again.", line.trim()),
}
};
println!("Got it: {age}");
}
twelve
12.5
12
How old are you?
'twelve' isn't a whole number, try again.
How old are you?
'12.5' isn't a whole number, try again.
How old are you?
Got it: 12
Note the bytes == 0 check: without it, a program that runs out of input would read empty lines forever, failing to parse each one. Always ask "what happens at the end of the input?".
At work: larger programs rarely match on every parse inside main. They pass errors up to one place that reports them, using Result return types and the ? operator (modules 19 and 39). match on Ok/Err is the building block underneath all of that.
Common mistakes
- Leaving out an arm: error E0004, as shown above.
- Arms that give different types.
Ok(n) => n, Err(_) => "bad"giveserror[E0308]: `match` arms have incompatible types: one arm is a number, the other a&str. Every arm that produces a value must produce the same type. - No type for
parse:match text.parse() { Ok(n) => println!("{n}"), ... }doesn't say what to parse into (E0284,type annotations needed). Useparse::<i32>()or a typedlet.
Your task
Read lines until the end of the input. Each line should be a whole number (an order quantity, negative for a return), but some lines are garbage. Add up the valid numbers, count every other line as skipped (blank lines too), and print:
total: 15
skipped: 2
Challenge 2
Read one line: an age (a whole number, never negative). If it's valid, print the age next year, like next year: 31. Otherwise print what was typed (trimmed), like invalid age: abc.
Challenge 3
A guessing game with a fixed secret number, 7. Read guesses one per line until the end of the input. For each line:
- not a whole number →
not a number: <the trimmed text>(it doesn't count as a guess) - too small →
too low, too big →too high - correct →
correct! guesses: <number of valid guesses>, then stop reading
If the input ends without a correct guess, print out of guesses.