当前位置: 首页 > news >正文

Rust中错误处理机制

Rust 的错误处理是通过Result类型和panic!宏两个层次来完成的:

类型含义错误处理
可恢复错误程序逻辑上可以预期并处理,比如文件不存在、网络超时使用 Result<T, E>
不可恢复错误程序出现 bug 或严重问题,无法继续运行使用 panic!
可选值缺失一个值可能存在(Some(T))或不存在(None),避免空指针问题使用Option<T>

可恢复错误(Result<T,E>)

Result是一个 泛型枚举,强制调用者处理成功和失败两种情况(是零开销的,由编译器静态保证必须处理):

  • T:成功时返回的值类型;
  • E:错误时返回的错误类型。
use std::fs::File;
use std::io::Error;fn open_file() -> Result<File, Error> {let file = File::open("hello.txt");match file {Ok(f) => Ok(f),Err(e) => Err(e),}
}

常用方法

Result 提供了大量链式方法,用于灵活地处理成功或失败分支

方法说明
.is_ok()是否是 Ok
.is_err()是否是 Err
.ok()取值,返回Option<T>
.err()取错误,返回Option<E>
.unwrap()成功取值T,否则 panic
.expect(msg)成功取值T,否则 panic 并打印 msg
.unwrap_or(default)失败则返回默认值
.unwrap_or_else(f)失败时执行函数生成默认值
.map(f)若是Ok,应用函数F(T) -> U到内部值;否则返回原Err
.map_err(f)Err执行map
.and_then(f)若是Ok,应用函数F(T) -> Result<U, E>(处理T时可能返回错误)到内部值;否则返回原Err
.or_else(f)Err执行f

map与unwrap示例:

fn parse_and_double(vec: Vec<&str>) -> Vec<String> {vec.into_iter().map(|s| {s.parse::<i32>().map(|n| (n * 2).to_string()).unwrap_or_else(|_| s.to_string())}).collect()
}pub fn test_parse_and_double() {let vec = vec!["1", "2", "three", "4"];let doubled = parse_and_double(vec);assert_eq!(doubled, vec!["2", "4", "three", "8"]);
}

unwrapexpect

快速处理错误,失败时会panic:

  • unwrap():若ResultOk(v)则返回v;若为Err(e)则触发panic!(适合 “理论上不可能失败” 的场景)。
  • expect(msg):功能与unwrap类似,但可自定义panic!的错误消息(更易调试)。
use std::fs::read_to_string;fn main() {// 若文件不存在,unwrap会paniclet content = read_to_string("test.txt").unwrap(); // 自定义panic消息let content = read_to_string("test.txt").expect("读取test.txt失败");
}

错误传播(? 运算符)

? 是 Rust 中最常用的错误传播工具

  • 如果是 Ok(t),提取 t
  • 如果是 Err(e),提前返回当前函数,并将错误向上传递。

文件读写示例

use std::fs::File;
use std::io::{self, Read};fn read_username() -> Result<String, io::Error> {let mut s = String::new();File::open("username.txt")?.read_to_string(&mut s)?;Ok(s)
}// 等价于
fn read_username() -> Result<String, io::Error> {let mut s = String::new();let f = match File::open("username.txt") {Ok(file) => file,Err(e) => return Err(e),};match f.read_to_string(&mut s) {Ok(_) => Ok(s),Err(e) => return Err(e),}
}

当使用 ? 时,错误类型会自动通过 From trait 转换。这意味着可以组合不同来源的错误,只要能被转换到同一个错误类型。

use std::num::ParseIntError;
use std::io;#[derive(Debug)]
enum MyError {Io(io::Error),Parse(ParseIntError),
}impl From<io::Error> for MyError {fn from(e: io::Error) -> Self { MyError::Io(e) }
}
impl From<ParseIntError> for MyError {fn from(e: ParseIntError) -> Self { MyError::Parse(e) }
}fn read_and_parse() -> Result<i32, MyError> {let s = std::fs::read_to_string("num.txt")?; // io::Error → MyErrorlet num = s.trim().parse::<i32>()?;          // ParseIntError → MyErrorOk(num)
}

Option枚举:处理’空值’

Option虽不直接表示 “错误”,但用于处理 “可能没有值” 的场景(避免空指针异常);可认为OptionResult 的一个简化版本(错误类型固定为 ()) 。

enum Option<T> {Some(T), // 有值:包含具体值(类型为T)None,    // 无值:表示“空”
}

Result类似,可通过match?unwrap等方式处理;Option中的常用方法

方法功能
is_some()
/ is_none()
检查是否有值
unwrap()直接取值(若为 None 则 panic)
unwrap_or(default)若为 None返回默认值
unwrap_or_else(f)若为 None执行函数返回值
map(f)映射 Some 中的值
and_then(f)链式操作,f返回 Option
ok_or(err)转换为 Result
filter(pred)条件筛选
fn find_user(id: u32) -> Option<String> {if id == 1 {Some("Alice".to_string()) // 找到用户} else {None // 未找到用户}
}fn main() {// 方式1:match匹配match find_user(1) {Some(name) => println!("找到用户:{}", name),None => println!("用户不存在"),}// 方式2:?运算符(需函数返回Option)fn get_username(id: u32) -> Option<String> {let name = find_user(id)?; // 若为None,直接返回NoneSome(name)}// 方式3:unwrap(不存在则panic)let name = find_user(1).unwrap(); // 若为None,panic
}

自定义错误类型

Result中的E 可以是任意类型,因此我们可以:

  • 使用 String(简单但不方便匹配)
  • 使用 Box<dyn Error>(灵活但缺少结构化)
  • 定义自己的错误类型(推荐方式)

基本要求

自定义错误类型需要满足:

  • 必须实现std::error::Error trait:使其成为标准错误类型;
  • 必须实现std::fmt::Display trait:以提供用户友好的错误信息;
use std::fmt;
use std::error::Error;
use std::io;// 自定义错误枚举:包含两种错误类型
#[derive(Debug)] // 必须实现Debug
pub enum MyError {Io(io::Error), // 包装IO错误Parse(String), // 解析错误(携带错误信息)
}// 实现Display trait:定义错误的用户可见信息
impl fmt::Display for MyError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {MyError::Io(e) => write!(f, "IO操作失败: {}", e),MyError::Parse(msg) => write!(f, "解析失败: {}", msg),}}
}// 现 std::error::Error(让它成为标准错误类型)
impl Error for MyError {// (可选)实现source方法:fn source(&self) -> Option<&(dyn Error + 'static)> {match self {MyError::Io(e) => Some(e), // IO错误的底层是io::ErrorMyError::Parse(_) => None, // 解析错误没有底层错误}}
}pub fn test_my_error() -> Result<(), MyError> {// let e = MyError::Parse("无效的整数".to_string());let e = MyError::Io(io::Error::new(io::ErrorKind::Other, "自定义IO错误"));println!("Error: {}", e);Err(e)
}

实现 From

为错误类型实现 From,这样就能使用 ? 运算符自动转换错误

impl From<std::io::Error> for MyError {fn from(err: std::io::Error) -> Self {MyError::Io(err)}
}impl From<std::num::ParseIntError> for MyError {fn from(err: std::num::ParseIntError) -> Self {MyError::Parse(err)}
}fn read_number_from_file() -> Result<i32, MyError> {let content = std::fs::read_to_string("data.txt")?; // 自动转成 MyError::Iolet num = content.trim().parse::<i32>()?;           // 自动转成 MyError::ParseOk(num)
}

三方库简化错误定义

手写 DisplayFrom 很繁琐,Rust 社区提供了两个常用库。

thiserror#[derive(Error)] )-定义结构化错误 :

  • #[derive(Error, Debug)]:自动生成 ErrorDebug trait 的实现。
  • #[error("消息模板")]:定义 Display trait 的输出格式。
  • #[from]:自动实现 From<T> trait(T 是字段类型),允许通过 ? 运算符将 T 类型的错误自动转换为当前自定义错误。
use thiserror::Error;#[derive(Debug, Error)]
pub enum MyError {#[error("IO错误: {0}")]Io(#[from] std::io::Error),#[error("解析错误: {0}")]Parse(#[from] std::num::ParseIntError),#[error("未找到数据")]NotFound,
}

anyhow-简化上层错误处理:

  • anyhow::Result<T> 实际是 Result<T, anyhow::Error>
  • context 方法会将原始错误,并添加额外描述,便于追踪错误发生的场景。
use anyhow::{Result, Context};// 函数返回anyhow::Error,可容纳任何实现Error trait的类型
pub fn test_anyhow() -> Result<()> {test_my_error().context("test failed")?; // 为错误添加上下文Ok(())
}

不可恢复错误(panic!)

当程序遇到不可恢复的错误(如违反内存安全、逻辑断言失败)时,使用panic!宏触发程序终止。默认会导致程序终止,但在某些场景下(如隔离第三方库的崩溃、实现容错机制),我们需要’安全捕获’panic 以避免程序整体崩溃。

fn divide(a: i32, b: i32) -> i32 {if b == 0 {panic!("除数不能为0"); // 逻辑错误:不允许除0}a / b
}

panic! 被触发时,Rust 默认会执行 “栈展开”(可在Cargo.toml中添加panic = "abort" 直接终止,不进行展开):

  • panic! 发生的位置开始,沿着调用栈向上回溯。
  • 依次调用每个栈帧中局部变量的析构函数(清理资源,如关闭文件、释放锁等)。
  • 最终打印错误信息(包含 panic! 的位置和消息)并终止程序。

这种行为保证了 “资源安全”—— 即使程序崩溃,也不会泄露资源。

catch_unwind

atch_unwind 接收一个闭包作为参数,返回 Result<(), Box<dyn Any + Send>>

  • 若闭包执行中未发生 panic,返回 Ok(())
  • 若发生 panic,返回 Err(cause),其中 causepanic! 传递的值(包装为 Box<dyn Any + Send>)。
use std::panic;fn main() {// 捕获闭包中的paniclet result = panic::catch_unwind(|| {println!("执行可能 panic 的代码");panic!("发生错误!"); // 触发panic});// 处理捕获结果match result {Ok(_) => println!("未发生 panic"),Err(cause) => {// 将cause转换为具体类型(通常是&str或String)if let Some(msg) = cause.downcast_ref::<&str>() {println!("捕获到 panic:{}", msg);} else {println!("捕获到未知 panic");}}}// 程序继续执行(不会终止)println!("捕获 panic 后,程序继续运行");
}

catch_unwind 并非万能,存在以下关键限制:

  • 仅能捕获 “栈展开” 的 panic:若程序配置为 panic = "abort"catch_unwind 无法捕获(因为不会触发栈展开)。
  • 无法捕获所有终止情况:如调用 std::process::abort()、操作系统信号(如 SIGKILL)导致的终止,catch_unwind 无效。
  • 闭包需满足 Send 约束:catch_unwind 的闭包参数要求 FnOnce() + Send,这意味着闭包中不能包含非 Send 类型(如 RcRefCell 等线程不安全类型)。
  • 程序状态可能不稳定:panic 发生时,栈展开会清理局部资源,但全局状态、共享资源可能处于不一致状态(如部分更新的缓存)。捕获 panic 后需谨慎处理,避免依赖不稳定状态。

资源与不变式

当线程在持有 Mutex 时 panic,该 Mutex 会被标记为“poisoned”(污染)。随后 lock() 会返回 PoisonError,表示需要小心处理共享状态可能处于不一致状态。

use std::sync::Mutex;let m = Mutex::new(0);
// 线程 panic 造成 poison 的示例请参见文档
match m.lock() {Ok(mut guard) => *guard += 1,Err(poisoned) => {// 取回锁仍然可用的数据,但知道它可能不一致let mut guard = poisoned.into_inner();*guard += 1;}
}

有效载荷payload

panic!() 可以携带 &'static strString 等任意 Any + Send 类型作为 payload。捕获后可以尝试 downcast_ref 来读取消息:

use std::panic;if let Err(payload) = panic::catch_unwind(|| panic!("oh no")) {if let Some(s) = payload.downcast_ref::<&str>() {println!("&str payload: {}", s);} else if let Some(s) = payload.downcast_ref::<String>() {println!("String payload: {}", s);} else {println!("未知 payload 类型");}
}
http://www.dtcms.com/a/532636.html

相关文章:

  • Ubuntu 24.04上安装MySQL 8.0
  • Java基于SpringBoot的高校报修与互助平台小程序【附源码、文档说明】
  • 工信部icp备案流程关键词在线优化
  • 做视频的模板下载网站ppt汇报模板免费下载
  • 10.16-10.25力扣计数刷题
  • 在K8s中部署多个ASP.NET Core实例
  • 14.如何利用ArcGIS将矢量线、面的坐标数据保存为txt
  • 网站开发者模式怎么打开做策划网站推广怎么写简历
  • zynq ttc pwm例子
  • 【底层机制】linux IO 为什么要有进程表项、文件表项、v节点表项、i节点表项
  • 怎么用wordpress修改网站源码镇江网站网站建设
  • 设计方案表网站名称汉台网站制作
  • git误合并两分支如何回退
  • 【Linux系统编程】编译器gcc/g++
  • LeetCode 面试经典 150_链表_K 个一组翻转链表(61_25_C++_困难)(四指针法;头插法)
  • 做一个简单网站多少钱建设银行网站买手机
  • Ubuntu 22.04上安装Vivado2023.1(离线方式)
  • 使用 OpenAI SDK 调用阿里云 Qwen 模型:从基础到提示词工程实战
  • HTTPS 高频考点
  • 安徽网站建设 网新线上推广100种方式
  • 东莞专业做网站优化用vs2010做网站登入
  • 若依框架学习第二天:功能改造与问题攻坚实战 (2)
  • 为什么要学深度学习?——从“传统编程”到“数据驱动”的思维跃迁(附AI落地案例)
  • 简述网站建设优坏的评价标准wordpress 手机访问不了
  • 浙江网站改版设计公司网站策划书结尾
  • 网站建设 招标公告域名注册的网站
  • 【OC】UIKit常用组件适配iOS 26
  • 自适应微网站开发舟山网站设计
  • 南京高端网站开发朝城做网站公司
  • 26.UE-游戏逆向-绘制骨骼编号