Rust encourages developers to explicitly handle errors and provides powerful tools to manage them safely. After exploring unrecoverable errors with panic!
in Day 15, today I explored the comprehensive mechanisms around recoverable errors using Result<T, E>
. This blog post covers every nuance, code pattern, and real-world scenario to help you master this vital Rust concept.
πΉ Understanding Result<T, E>
Enum
enum Result<T, E> {
Ok(T),
Err(E),
}
-
T
is the success type. -
E
is the error type. - Common in operations like file handling, networking, parsing, etc.
π Basic Example: Opening a File
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
}
-
File::open
returnsResult<File, std::io::Error>
. - Tells us clearly if the file open succeeded or failed.
π Match for Handling Result
use std::fs::File;
fn main() {
let greeting_file = match File::open("hello.txt") {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {error:?}"),
};
}
Output if the file doesn't exist:
Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
π Matching Different Error Types
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = match File::open("hello.txt") {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => File::create("hello.txt")
.unwrap_or_else(|e| panic!("Problem creating the file: {e:?}")),
_ => panic!("Problem opening the file: {error:?}"),
},
};
}
- Enables more control: retry, fallback, or recover logic based on specific error types.
π Cleaner with unwrap_or_else
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {error:?}");
})
} else {
panic!("Problem opening the file: {error:?}");
}
});
π© Shortcuts for Panic on Error: unwrap()
and expect()
let greeting_file = File::open("hello.txt").unwrap();
let greeting_file = File::open("hello.txt")
.expect("hello.txt should be included in this project");
- Prefer
expect
in production to give context for the panic.
π§³ Propagating Errors Manually
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
β Propagating Errors Using ?
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
Even Shorter:
File::open("hello.txt")?.read_to_string(&mut username)?;
Shortest:
use std::fs;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
π ? Operator Rules
- Can only be used in functions returning
Result
,Option
, or types implementingFromResidual
.
fn main() {
let greeting_file = File::open("hello.txt")?; // β will not compile
}
Fix:
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
π§ ? with Option<T>
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
- Extracts early on
None
- Powerful for chaining fallible operations
π€ Final Notes
- Use
match
orunwrap_or_else
for fine control. - Prefer
expect
overunwrap
in production. - Use
?
for clean propagation. - Handle different error kinds with
ErrorKind
. -
main
can returnResult<(), Box<dyn Error>>
for flexibility.
This completes a comprehensive tour of recoverable error handling in Rust. These tools help write reliable and user-friendly applications.
Stay tuned for Day 17 β we will explore Generic Type.
Top comments (0)