Working Code
In Lesson 07 you used unwrap() in the number-guessing game. Back then, the explanation was just "if there's an error, stop the program." Now it's time to understand what that really means.
fn main() {
// Scenario: reading a number from input
let input = "42";
let number: i32 = input.parse().unwrap();
println!("Number: {number}");
let bad_input = "abc";
let result: Result<i32, _> = bad_input.parse();
println!("Result: {:?}", result);
}
Run it! "42" converts fine, but "abc" gives you Err(...). If you'd written bad_input.parse().unwrap(), the program would have crashed.
Try It Yourself
- Try running
bad_input.parse().unwrap()directly. What error message do you get? - Change
inputto"99999999999999999999". What happens when it overflows thei32range?
"Why?" — Two Kinds of Errors
Rust divides errors into two categories:
| | Unrecoverable (panic!) | Recoverable (Result) | | -------- | ------------------------------------------------ | ------------------------------------------ | | Analogy | Building fire — evacuate! | Failed delivery — retry | | When | Programming bugs, should-never-happen situations | File not found, network disconnected, etc. | | Handling | Program terminates immediately | Receive the error and respond |
panic! — Unrecoverable Errors
panic! means "we can't continue — shut everything down."
fn main() {
println!("Starting!");
panic!("Something went horribly wrong!");
println!("This line never runs");
}
Out-of-bounds indexing also triggers a panic. You saw this in Lesson 06:
fn main() {
let v = vec![1, 2, 3];
println!("{}", v[10]); // panic!
}
Result — Like Checking a Delivery Status
Result is an enum that holds either success or failure. The enums from Lesson 13 make another appearance!
enum Result<T, E> {
Ok(T), // Success! Contains the value
Err(E), // Failure! Contains error info
}
Think of it like checking a package delivery:
Ok(value)— Delivered! Here's your itemErr(error)— Delivery failed! Here's the reason
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(a / b)
}
}
fn main() {
let result1 = divide(10.0, 3.0);
let result2 = divide(10.0, 0.0);
println!("10 / 3 = {:?}", result1);
println!("10 / 0 = {:?}", result2);
}
Handling Result With match
You can handle Result with the match you learned in Lesson 13:
fn main() {
let input = "42";
let result: Result<i32, _> = input.parse();
match result {
Ok(number) => println!("Conversion succeeded: {number}"),
Err(e) => println!("Conversion failed: {e}"),
}
}
This is exactly the pattern from the number-guessing game in Lesson 07:
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please enter a number!");
continue;
}
};
Back then you didn't know match well, but now you can see it's handling Result's Ok and Err variants.
unwrap() — Convenient but Dangerous
unwrap() is the simplest way to extract a value from a Result. But it panics on Err:
fn main() {
let ok_result: Result<i32, String> = Ok(42);
let value = ok_result.unwrap(); // 42
println!("{value}");
// let err_result: Result<i32, String> =
// Err(String::from("error!"));
// err_result.unwrap(); // panic!
}
Here's what unwrap() does:
Ok(value)-> extracts the valueErr(error)-> panics and crashes the program
In Lesson 07 you wrote io::stdin().read_line(&mut guess).unwrap(). If reading input failed, the program would just die. Fine for practice code, but dangerous in real programs.
Learn More: expect() — A Better unwrap()
expect() works like unwrap() but lets you specify a custom error message:
fn main() {
let input = "abc";
let number: i32 = input
.parse()
.expect("Failed to convert to number");
// panic message: "Failed to convert to number: ..."
}
It's easier to find where an error occurred, making expect() better than unwrap(). But both still cause a panic.
You'll learn the ? operator for elegantly propagating errors in Phase 3!
Exercise 1. Complete the function to parse a string into i32. Return 0 as the default if parsing fails.
fn parse_or_default(s: &str) -> i32 {
// Complete this
// Hint: use match on s.parse::<i32>()
}
fn main() {
println!("{}", parse_or_default("42")); // 42
println!("{}", parse_or_default("abc")); // 0
println!("{}", parse_or_default("")); // 0
}
Exercise 2. Complete the find_item function. Return Ok(index) if found, or Err(message) if not.
fn find_item(items: &[&str], target: &str) -> Result<usize, String> {
// Complete this
// Hint: use for with enumerate()
}
fn main() {
let fruits = vec!["apple", "banana", "grape"];
match find_item(&fruits, "banana") {
Ok(idx) => println!("banana is at index {idx}!"),
Err(msg) => println!("{msg}"),
}
match find_item(&fruits, "strawberry") {
Ok(idx) => println!("strawberry is at index {idx}!"),
Err(msg) => println!("{msg}"),
}
}
Q1. What is the biggest difference between panic! and Result?
- A)
panic!is slow andResultis fast - B)
panic!immediately stops the program;Resultreturns the error as a value - C)
panic!handles numbers only;Resulthandles strings only - D) No difference
Q2. Which statement about unwrap() is correct?
- A) It always returns
None - B) On
Okit extracts the value; onErrit triggers a panic - C) It ignores errors and returns an empty value
- D) It prints the error and continues execution
Q3. What does this code print?
fn main() {
let result: Result<i32, &str> = Err("failure");
let value = match result {
Ok(n) => n,
Err(_) => -1,
};
println!("{value}");
}
- A) "failure"
- B) -1
- C) 0
- D) The program panics