ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Comprehensive Rust 课程精讲:用 `?` Try Operator 优雅处理 `Result` 错误

Comprehensive Rust 课程精讲:用 `?` Try Operator 优雅处理 `Result` 错误 Comprehensive Rust 课程精讲用?Try Operator 优雅处理Result错误【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust导读本文围绕 Google Android 团队 Rust 课程Comprehensive Rust中「Try Operator」一节src/error-handling/try.md展开系统讲解?运算符如何把繁琐的match ... return Err(...)样板代码压缩为一行并深入其背后的类型转换语义From::from、与Result/Option/动态错误类型的组合规则以及课程配套练习中的真实应用。读完本文你将能写出既简洁又具备完整错误语义的 Rust 代码并理解?在函数签名上的约束。为什么需要 Try OperatorResult匹配的痛点Rust 的核心错误处理机制是Result枚举。课程在 src/error-handling/result.md 中指出一个函数是否可能出错直接编码在其类型签名里——它必须返回Result。与Option类似你不可能忘记处理错误不先对Result做模式匹配就无法取出成功值或错误值。但正因如此逐层匹配Result会让代码迅速变得臃肿。Try Operator?正是为消除这种样板而生的它把常见的match some_expression { Ok(value) value, Err(err) return Err(err), }压缩成一行some_expression?两者语义完全等价成功时取出Ok中的值失败时把错误原样返回给调用者。实战对比用?重写文件读取逻辑课程在 src/error-handling/try.md 中给出了一个典型例子——读取配置文件中的用户名。未使用?的版本需要对每次可能失败的操作手工匹配use std::io::Read; use std::{fs, io}; fn read_username(path: str) - ResultString, io::Error { let username_file_result fs::File::open(path); let mut username_file match username_file_result { Ok(file) file, Err(err) return Err(err), }; let mut username String::new(); match username_file.read_to_string(mut username) { Ok(_) Ok(username), Err(err) Err(err), } } fn main() { //fs::write(config.dat, alice).unwrap(); let username read_username(config.dat); println!(username or error: {username:?}); }其中fs::File::open和read_to_string都可能返回io::Error两者正是read_username返回类型ResultString, io::Error的错误类型。使用?重写后read_username变为fn read_username(path: str) - ResultString, io::Error { let mut username String::new(); fs::File::open(path)?.read_to_string(mut username)?; Ok(username) }两处?分别对应原来的两次match文件打开失败或读取失败时io::Error会被直接返回给调用者。这就是课程要求的核心练习目标。可验证的边界情况read_username的返回值既可能是Ok(string)也可能是Err(error)调用方必须同时处理两种可能用fs::write(config.dat, alice)生成文件后可以测试三种场景文件不存在open返回Err、空文件读取成功但内容为空、含有用户名正常返回Ok。?的精确展开Try Conversions?的实际展开比字面语义更复杂。课程在 src/error-handling/try-conversions.md 中给出了精确定义expression?等价于match expression { Ok(value) value, Err(err) return Err(From::from(err)), }关键在于From::from(err)?会尝试把错误类型转换为函数声明的返回错误类型。这使我们可以把底层细粒度错误如io::Error自动封装进更高层的自定义错误类型而无需逐处手写转换。示例自定义错误类型 自动转换课程示例定义了一个ReadUsernameError枚举将io::Error与业务语义错误空用户名统一起来use std::error::Error; use std::io::Read; use std::{fmt, fs, io}; #[derive(Debug)] enum ReadUsernameError { IoError(io::Error), EmptyUsername(String), } impl Error for ReadUsernameError {} impl fmt::Display for ReadUsernameError { fn fmt(self, f: mut fmt::Formatter) - fmt::Result { match self { Self::IoError(e) write!(f, I/O error: {e}), Self::EmptyUsername(path) write!(f, Found no username in {path}), } } } impl Fromio::Error for ReadUsernameError { fn from(err: io::Error) - Self { Self::IoError(err) } } fn read_username(path: str) - ResultString, ReadUsernameError { let mut username String::with_capacity(100); fs::File::open(path)?.read_to_string(mut username)?; if username.is_empty() { return Err(ReadUsernameError::EmptyUsername(String::from(path))); } Ok(username) } fn main() { //std::fs::write(config.dat, ).unwrap(); let username read_username(config.dat); println!(username or error: {username:?}); }这里的impl Fromio::Error for ReadUsernameError正是?自动转换能够成立的前提read_username返回ResultString, ReadUsernameError内部fs::File::open(path)?产生的是io::Error?借助From实现把它包装为ReadUsernameError::IoError然后返回。类型兼容性规则务必牢记返回ResultT, ErrorOuter的函数只能对类型为ResultU, ErrorInner的值使用?前提是ErrorOuter与ErrorInner类型相同或ErrorOuter实现了FromErrorInnerFrom实现的常见替代方案是Result::map_err尤其在转换只出现一次时更直接Option之间使用?没有类型兼容性要求返回OptionT的函数可对任意OptionU使用?None直接返回None返回Result的函数不能对Option使用?反之亦然需要跨类型时用Option::ok_orOption→Result或Result::okResult→Option转换。与动态错误类型结合Boxdyn Error与?当不想为所有错误情况手写枚举时可以用 trait object 容纳任意错误。课程在 src/error-handling/error.md 中演示use std::error::Error; use std::fs; use std::io::Read; fn read_count(path: str) - Resulti32, Boxdyn Error { let mut count_str String::new(); fs::File::open(path)?.read_to_string(mut count_str)?; let count: i32 count_str.parse()?; Ok(count) } fn main() { fs::write(count.dat, 1i3).unwrap(); match read_count(count.dat) { Ok(count) println!(Count: {count}), Err(err) println!(Error: {err}), } }read_count可能返回std::io::Error文件操作或std::num::ParseIntErrorString::parseBoxdyn Error统一收纳装箱错误省代码但放弃了按错误类型分别处理的精确性因此一般不适合作为库的公开 API在只需显示错误信息的程序中则是好选择自定义错误类型想被装箱必须实现std::error::Errortrait参考上一节ReadUsernameError的实现方式。让main也使用?Terminationtrait课程在 src/error-handling/try.md 的讲解要点中特别指出main可以返回Result(), E前提是E实现了std::process::Termination。实际使用中这通常意味着E实现了Debug——此时程序会在出错时打印Err变体并以非零退出码结束。这使得命令行工具可以用fn main() - Result(), Boxdyn Error这样的签名配合?贯穿全函数链。从练习看?的工程价值表达式求值器重构课程配套练习 src/error-handling/exercise.md 要求把第二天写过的表达式求值器重构为使用Result原实现遇到除零直接panic!而练习要求改为返回错误。配套代码 src/error-handling/exercise.rs 给出了完整的类型定义与参考答案#[derive(PartialEq, Eq, Debug)] struct DivideByZeroError; fn eval(e: Expression) - Resulti64, DivideByZeroError { match e { Expression::Op { op, left, right } { let left eval(*left)?; let right eval(*right)?; Ok(match op { Operation::Add left right, Operation::Sub left - right, Operation::Mul left * right, Operation::Div { if right 0 { return Err(DivideByZeroError); } else { left / right } } }) } Expression::Value(v) Ok(v), } }这个例子最能体现?的威力递归求值中子表达式一旦出错eval(*left)?一行即把错误向上传播无需为每一层递归手写match与return。练习还附带了单元测试同文件中的test_error与test_ok分别断言除零返回Err(DivideByZeroError)、正常减法返回Ok(10)可随时用cargo test验证。更进一步thiserror与anyhow课程的错误处理章节见 src/error-handling/error-handling 目录 下的各小节还介绍了两个与?配合使用的生态 crate二者均已声明在 src/error-handling/Cargo.toml 中thiserror用 derive 宏消除自定义错误类型的样板代码自动生成FromT、Display与Errortrait 的实现。上面的ReadUsernameError可改写为use std::io::Read; use std::{fs, io}; use thiserror::Error; #[derive(Debug, Error)] enum ReadUsernameError { #[error(I/O error: {0})] IoError(#[from] io::Error), #[error(Found no username in {0})] EmptyUsername(String), }#[from]属性自动生成Fromio::Error实现#[error]消息用于派生Display。注意thiserror::Error是宏std::error::Error是 trait二者共享效果但分属不同命名空间。anyhow提供带上下文信息的丰富错误类型可形成程序在执行到出错前做了什么的语义追踪链。anyhow::ResultV是ResultV, anyhow::Error的类型别名其内部本质上是Boxdyn Error的包装因此同样不适合库的公开 API但在应用层被广泛使用其行为与 Go 的error类型相似ResultT, anyhow::Error近似(T, error)约定。结合Contexttrait?可以携带调用位置的上下文信息use anyhow::{Context, Result, bail}; use std::fs; use std::io::Read; use thiserror::Error; #[derive(Clone, Debug, Eq, Error, PartialEq)] #[error(Found no username in {0})] struct EmptyUsernameError(String); fn read_username(path: str) - ResultString { let mut username String::with_capacity(100); fs::File::open(path) .with_context(|| format!(Failed to open {path}))? .read_to_string(mut username) .context(Failed to read)?; if username.is_empty() { bail!(EmptyUsernameError(path.to_string())); } Ok(username) }当?传播anyhow::Error时整条上下文链会一并保留最终调试输出{:?}即可看到完整追踪。小结?是match { Ok(v) v, Err(e) return Err(e) }的语法糖真正展开时错误会经过From::from转换成功路径上?在Result与Option上的行为一致取出内部值失败路径上Result要求错误类型相同或可From转换Option则直接返回Nonemain返回Result(), EE: Debug时可直接用?出错自动打印并返回非零退出码与Boxdyn Error、thiserror、anyhow组合可以分别获得统一收纳零样板自定义错误带上下文的错误追踪三种能力按库/应用场景取舍课程原文位于 src/error-handling/try.md配套概念见 try-conversions.md、result.md、error.md练习与答案见 exercise.md 与 exercise.rs。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表