Rust的错误处理

发布时间:2026/7/27 19:00:41

Rust的错误处理 概述Rust 偷师 Haskell构建了对标Maybe的Option 类型和对标Either 的Result 类型Option 和 ResultOption 是一个 enum其定义如下pubenumOptionT{None,Some(T)}它可以承载有值 / 无值这种最简单的错误类型Result 是一个更加复杂的 enum, 其定义如下:#[must_use this Result may be an Err variant, which should be handle]pubenumResultT,E{Ok(T),Err(E)}当函数出错时可以返回Err(E), 否则 Ok(T)Result 类型声明时还有个must_use 的标注编译器会对有 must_use 标注的所有类型做特殊处理如果该类型对应的值没有被显式使用则会告警。这样保证错误被妥善处理操作符所以在Rust 代码中如果你只想传播错误不想就地处理可以用 操作符usestd::fs::File;usestd::io::Read;fnread_file(name:str)-ResultString,std::io::Error{letmutfFile::open(name)?;letmutcontentsString::new();f.read_to_string(mutcontents);Ok(contents);}通过 操作符Rust 让错误传播的代价和异常处理不相上下同时又避免了异常处理的诸多问题? 操作符内部被展开成类似这样的代码matchresult{Ok(v)v,Err(e)returnErr(e.into())}所有我们可以方便写出类似这样的代码简洁易懂可读性很强fut.await?.process()?.next().await?;整个代码的执行流程如下:虽然 操作符使用起来非常方便但要注意在不同的错误类型之间是无法直接使用的需要实现From trait 在二者之间建立起转换的桥梁这会带来额外的麻烦函数式错误处理Rust 还为Option 和 Result 提供了大量的辅助函数如 map / map_err / add_then 你可以很方便地处理数据结构中部分情况通过这些函数可以很方便地对错误处理引入 Railroad oriented programming 范式。比如用户注册的流程你需要校验用户输入对数据进行处理转换然后存入数据中Ok(data).add_then(validate).add_then(process).map(transform).and_then(store).map_error(...)执行流程如下图所示:此外Option 和 Result 的互相转换也很方便这也得益于Rust 构建的强大的函数式编程能力panic! 和 catch_unwind使用 Option 和 Result 是 Rust 中处理错误的首选绝大多数时候我们也应该使用但Rust 也提供了特殊的异常处理能力在 Rust 看来一旦你需要抛出异常那抛出的一定是严重的错误。所以Rust 跟 Golang 一样使用了诸如panic! 这样的字眼警示开发者想清楚了再使用我在使用Option 和 Result 类型时开发者也可以对其unwarp() 或者 expect() 强制把Option 和 ReulstT, E 转换成 T如果无法完成这种转换也会panic! 出来一般而言panic! 是 不可恢复或者不想恢复错误。希望在此刻程序终止运行并得到崩溃信息比如下面的代码它解析 noise protoco 的协议变量letparams:NoiseParamsNoise_XX_25519_AESGCM_SHA256.parse().unwrap();如果开发者小小心把协议变量写错了最佳的方式是立刻panic! 出来让错误立刻暴露以便解决这个问题有些场景下也希望能够像异常处理那样能够栈回溯把环境恢复到捕获异常的上下文。Rust 标准库下提供了catch_unwind(), 把调用栈回溯到 catch_unwind 这一刻作用和其他语言的 try {…} catch {…}usestd::painic;fnmain(){letresultpanic::catch_unwind(||{println(hello!);});assert!(result.is_ok());letresultpanic::catch_unwind(||{panic!(oh no!);});assert!(result.is_err());println!(panic captured: {:#?},result);}当然和异常处理一样并不意味你可以溢出这一特性我想这也是Rust 把 抛出异常称作 panic!, 而捕获异常称作 catch_unwind 的原因让初学者望而生畏不敢轻易使用catch_unwind 在某些场景下非常有用比如你在使用 Rust 为 erlang VM 撰写 NIF你不希望Rust 代码中的任何 panic! 导致 erlang VM 崩溃。因为崩溃是一个非常不好的体验它违背了 erlang 的 设计原则: process 可以 let it crash 但错误代码不该导致 VM 崩溃你就可以把Rust 代码整个封装在 catch_unwind() 函数所需要传入的闭包中这样一旦任何代码中包括第三方crates 的代码含有能够导致 panic! 的代码都会被捕获并被转换为一个ResultError trait 错误类型的转换为了规范这个代表错误的数据类型行为Rust 定义了 Error traitpubtraitError:DebugDisplay{fnsource(self)-Option(dynErrorstatic){...}fnbacktrace(self)-OptionBacktrace{...}fndescription(self)-str{...}fncause(self)-OptiondynError{...}}定义自己的数据类型然后为其实现 Error trait不过这样的工作已经有人替人简化了可以使用 thiserror 和 anyhow 来简化这个步骤。thiserror 提供了一个派生宏(drive macro) 来简化错误类型的定义usethiserror::Error;#[dervie(Error, Debug)]#[non_exhaustive]pubenumDataStoreError{#[error(data store disconnected)]Disconnect(#[from]io::Error),#[error(the data for key {0} is not available)]Redaction(String)#[error(invalid header (expected {expected:?}, found {found:?}))]InvalidHeader{expected:String,found:String},#[error(unknown data store error)]Unknown,}如果你在撰写一个Rust 库那么thiserror 可以很好地协助你对这个库里所有可能发生的错误进行建模而anyhow 实现了 anyhow::Error 和 任意符号 Error trait 的错误类型之间的转换让你可以使用操作符不必再手工转换错误类型anyhow 还可以让你容易抛出一些临时的错误而不必费力定义错误类型当然不提倡滥用这个能力建议开发前先用类似 thiserror 的库定义好你项目中主要的错误类型并随着项目的深入不断增加新的错误类型让系统中所有的潜在错误无所遁形

相关新闻