
panic!、Result、? 运算符:让错误不再是程序崩溃的理由
你有没有经历过这种绝望:程序跑了一半,突然崩溃,屏幕上只留下一行冰冷的错误信息,而你根本不知道发生了什么?
在 Rust 之前,我写 Python 的时候,try-except 就像个万能创可贴,哪里出错贴哪里。到了 Rust 才发现,人家把错误处理玩成了艺术——要么你优雅地处理错误,要么程序就明明白白地告诉你"我不干了"。
Rust 有两种错误处理方式:panic!(直接摆烂)和 Result(优雅处理)。今天咱们就搞清楚什么时候该摆烂,什么时候该优雅。
Rust 把错误分成两类:
1. 可恢复错误 - 比如文件不存在、网络超时、数据格式错误。这些错误可以处理,程序还能继续跑。
2. 不可恢复错误 - 比如数组越界、除零、逻辑 bug。这些错误说明程序状态已经不对了,继续跑只会更糟。
panic! 就是程序在说:"我遇到无法处理的错误了,不玩了!"然后立即终止。
什么时候用 panic!:
Result 是个枚举,只有两种状态:
Ok(T) - 成功了,返回值是 TErr(E) - 失败了,错误信息是 E这就像你去餐厅点菜:
Ok(你的菜) - 菜做好了Err(厨师的抱怨) - 菜没了/做糊了/厨师罢工了? 运算符的作用是:"如果成功了继续,如果失败了直接把错误返回给调用者"。
这就像你老板让你办事:
fn main() {
// 显式 panic
panic!("我不干了!");
// 常见 panic 场景
let nums = vec![, , ];
let num = nums[]; // ❌ panic! 索引越界
let x = ;
let y = / x; // ❌ panic! 除零(debug 模式)
}
use std::fs::File;
use std::io::Error;
fn open_file() -> Result<File, Error> {
// File::open 返回 Result<File, Error>
let file = File::open("hello.txt");
match file {
Ok(f) => {
println!("文件打开成功!");
Ok(f)
}
Err(e) => {
println!("文件打开失败:{}", e);
Err(e)
}
}
}
fn main() {
match open_file() {
Ok(_) => println!("搞定!"),
Err(_) => println!("完蛋!"),
}
}
use std::fs::File;
use std::io::{self, Read};
// 用 ? 简化错误处理
fn read_file() -> io::Result<String> {
let mut file = File::open("hello.txt")?; // 失败直接返回 Err
let mut contents = String::new();
file.read_to_string(&mut contents)?; // 失败直接返回 Err
Ok(contents)
}
// 链式调用 + ?
fn read_file_short() -> io::Result<String> {
let mut contents = String::new();
File::open("hello.txt")?
.read_to_string(&mut contents)?;
Ok(contents)
}
use std::fmt;
use std::error::Error;
// 定义自己的错误类型
#[derive(Debug)]
enum AppError {
FileNotFound(String),
InvalidData(String),
DatabaseError(String),
}
// 实现 Display trait
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::FileNotFound(path) => write!(f, "文件不存在:{}", path),
AppError::InvalidData(msg) => write!(f, "数据无效:{}", msg),
AppError::DatabaseError(msg) => write!(f, "数据库错误:{}", msg),
}
}
}
// 实现 Error trait
impl Error for AppError {}
// 使用自定义错误
fn load_config(path: &str) -> Result<String, AppError> {
if path.is_empty() {
return Err(AppError::FileNotFound("路径为空".to_string()));
}
// 模拟读取文件
Ok("配置内容".to_string())
}
fn main() {
match load_config("") {
Ok(config) => println!("配置:{}", config),
Err(e) => println!("错误:{}", e),
}
}
use std::fs::File;
use std::io;
use std::num::ParseIntError;
// 多种错误类型统一处理
fn read_number() -> Result<i32, Box<dyn std::error::Error>> {
let contents = std::fs::read_to_string("number.txt")?; // io::Error
let number: i32 = contents.trim().parse()?; // ParseIntError
Ok(number)
}
// 用 map_err 转换错误
fn parse_positive() -> Result<u32, String> {
let s = "42";
s.parse::<u32>()
.map_err(|e| format!("解析失败:{}", e))
.and_then(|n| {
if n > {
Ok(n)
} else {
Err("数字必须大于 0".to_string())
}
})
}
错误 1:忽略 Result
fn main() {
File::open("hello.txt"); // ⚠️ 警告!Result 被忽略
}
编译器说:
warning: unused `Result` that must be used
note: this may be a bug and should be handled
正确做法:
let _ = File::open("hello.txt"); // 显式忽略
// 或者
File::open("hello.txt").unwrap(); // panic 如果失败
// 或者
match File::open("hello.txt") {
Ok(f) => {/* 处理 */},
Err(e) => {/* 处理错误 */}
}
错误 2:滥用 unwrap()
fn main() {
let file = File::open("hello.txt").unwrap(); // ❌ 文件不存在就 panic
}
正确做法:
let file = match File::open("hello.txt") {
Ok(f) => f,
Err(e) => {
eprintln!("无法打开文件:{}", e);
return;
}
};
错误 3:? 用在 main 函数外
fn read_file() {
let contents = std::fs::read_to_string("hello.txt")?; // ❌ 编译错误
}
编译器说:
the `?` operator can only be used in a function that returns `Result` or `Option`
正确做法:
fn read_file() -> std::io::Result<()> {
let contents = std::fs::read_to_string("hello.txt")?;
Ok(())
}
fn process() -> Result<(), String> {
let file = File::open("hello.txt")?; // ❌ io::Error 不能转 String
Ok(())
}
解决:
fn process() -> Result<(), Box<dyn Error>> {
let file = File::open("hello.txt")?; // ✅ 统一用 Box<dyn Error>
Ok(())
}
fn validate(x: i32) -> Result<i32, String> {
if x < {
return Err("负数".to_string());
}
Ok(x * )
}
fn process() -> Result<(), String> {
let x = ;
let y = validate(x)?; // ✅ 可以
let z = validate(y)?; // ✅ 可以
// ... 一连串 ?
Ok(())
}
如果中间需要特殊处理某个错误,就别用 ?,用 match。
fn load_config() -> Config {
let config = Config::load();
config.unwrap() // ❌ 生产环境别这么干
}
正确做法: 返回 Result,让调用者决定怎么处理。
use std::fs;
use std::io;
use std::path::Path;
#[derive(Debug)]
enum ConfigError {
Io(io::Error),
Parse(String),
MissingField(String),
}
impl From<io::Error> for ConfigError {
fn from(err: io::Error) -> Self {
ConfigError::Io(err)
}
}
struct Config {
host: String,
port: u16,
}
impl Config {
fn load(path: &str) -> Result<Self, ConfigError> {
let contents = fs::read_to_string(path)?;
let mut host = None;
let mut port = None;
for line in contents.lines() {
let parts: Vec<&str> = line.split('=').collect();
if parts.len() != {
continue;
}
match parts[].trim() {
"host" => host = Some(parts[].trim().to_string()),
"port" => port = Some(parts[].trim().parse()
.map_err(|e| ConfigError::Parse(format!("端口解析失败:{}", e)))?),
_ => {}
}
}
Ok(Config {
host: host.ok_or_else(|| ConfigError::MissingField("host".to_string()))?,
port: port.ok_or_else(|| ConfigError::MissingField("port".to_string()))?,
})
}
}
fn main() {
match Config::load("config.txt") {
Ok(config) => println!("配置加载成功:{}:{}", config.host, config.port),
Err(e) => eprintln!("配置加载失败:{:?}", e),
}
}
#[derive(Debug)]
enum ValidationError {
TooShort,
TooLong,
InvalidCharacter(char),
Empty,
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ValidationError::TooShort => write!(f, "用户名太短"),
ValidationError::TooLong => write!(f, "用户名太长"),
ValidationError::InvalidCharacter(c) => write!(f, "包含非法字符:{}", c),
ValidationError::Empty => write!(f, "用户名不能为空"),
}
}
}
fn validate_username(username: &str) -> Result<String, ValidationError> {
if username.is_empty() {
return Err(ValidationError::Empty);
}
if username.len() < {
return Err(ValidationError::TooShort);
}
if username.len() > {
return Err(ValidationError::TooLong);
}
for c in username.chars() {
if !c.is_alphanumeric() && c != '_' {
return Err(ValidationError::InvalidCharacter(c));
}
}
Ok(username.to_string())
}
fn main() {
let usernames = vec!["", "ab", "valid_user", "very_long_username_that_is_too_long", "bad@user"];
for username in usernames {
match validate_username(username) {
Ok(name) => println!("✅ {} 验证通过", name),
Err(e) => println!("❌ {} 验证失败:{}", username, e),
}
}
}
use std::fs;
use std::io;
use std::path::PathBuf;
struct ProcessResult {
success: usize,
failed: usize,
errors: Vec<(String, String)>,
}
fn process_files(paths: &[String]) -> ProcessResult {
let mut result = ProcessResult {
success: ,
failed: ,
errors: Vec::new(),
};
for path in paths {
match process_single_file(path) {
Ok(_) => result.success += ,
Err(e) => {
result.failed += ;
result.errors.push((path.clone(), e.to_string()));
}
}
}
result
}
fn process_single_file(path: &str) -> io::Result<()> {
let contents = fs::read_to_string(path)?;
let processed = contents.to_uppercase();
fs::write(path, processed)?;
Ok(())
}
fn main() {
let files = vec![
"file1.txt".to_string(),
"file2.txt".to_string(),
"nonexistent.txt".to_string(),
];
let result = process_files(&files);
println!("处理完成:成功 {}, 失败 {}", result.success, result.failed);
for (path, error) in result.errors {
eprintln!("文件 {} 处理失败:{}", path, error);
}
}

Result,不可恢复的用 panic!Ok(T) 成功,Err(E) 失败,必须处理Result 或 OptionDisplay 和 Error trait下篇预告: 你有没有写过重复的代码?同样的逻辑,不同的类型,就要复制粘贴?泛型来拯救你了!一篇让你搞清楚泛型到底是个啥,为什么 Rust 的泛型这么强大!