Rust has no exceptions. Instead it separates two kinds of failure: bugs you cannot recover from stop the program with panic!, while operations that can legitimately fail return a Result that the caller must handle. The second half of this chapter covers the testing tools built into Cargo.
Panics versus recoverable errors
A panic! says "reaching this point means the program has a bug". Indexing past the end of a slice or calling unwrap() on None also panic. For expected failures, like a missing file or malformed input, return Result<T, E>.
use std::fs;
use std::io;
fn read_config(path: &str) -> Result<String, io::Error> {
let text = fs::read_to_string(path)?; // on failure, return the error
Ok(text.trim().to_string())
}
fn main() {
match read_config("config.txt") {
Ok(text) => println!("config: {text}"),
Err(e) => eprintln!("could not read config: {e}"),
}
}
unwrap() and expect("reason") panic on error. Keep them for examples, tests, and places where failure truly cannot happen, and even then prefer expect so the panic message explains your assumption.
The ? operator
? unwraps an Ok value or returns the Err from the current function immediately. If the error types differ, it converts via From into the function's error type. Inside a function that returns Option, it works on Option values as well.
use std::error::Error;
use std::num::ParseIntError;
fn sum_strs(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x: i32 = a.trim().parse()?;
let y: i32 = b.trim().parse()?;
Ok(x + y)
}
fn first_char_upper(s: &str) -> Option<char> {
let c = s.chars().next()?; // None for an empty string
Some(c.to_ascii_uppercase())
}
// main can return a Result; an Err is printed and the exit code signals failure
fn main() -> Result<(), Box<dyn Error>> {
println!("{}", sum_strs("2", " 40 ")?); // 42
println!("{:?}", first_char_upper("rust")); // Some('R')
println!("{:?}", sum_strs("2", "x")); // Err(ParseIntError { .. })
Ok(())
}
Box<dyn Error> can hold any error type, which makes it a convenient return type for main in small programs.
Custom error types
Libraries usually model their failure modes as an enum so callers can match on them. Implement Display and std::error::Error, and add From impls so ? converts underlying errors automatically.
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
Negative(i64),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Parse(e) => write!(f, "not a number: {e}"),
AppError::Negative(n) => write!(f, "negative values are not allowed: {n}"),
}
}
}
impl std::error::Error for AppError {}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::Parse(e)
}
}
fn parse_positive(s: &str) -> Result<i64, AppError> {
let n: i64 = s.parse()?; // ParseIntError becomes AppError
if n < 0 {
return Err(AppError::Negative(n));
}
Ok(n)
}
fn main() {
for input in ["42", "-7", "abc"] {
match parse_positive(input) {
Ok(n) => println!("ok: {n}"),
Err(e) => println!("error: {e}"),
}
}
}
0 条评论
登录 · 登录后即可发表评论。