使用Result类型已成为Rust中错误处理的关键方法之一。历史上,许多语言——例如C——通过特殊的返回值或全局变量来信号错误,这导致在忽略这些信号时经常出现错误。Rust通过显式的错误类型化,采用了enum Result<T, E>,这使得意外忽略错误变得不可能——编译器强制处理两个分支(成功和失败)。
问题:需要使错误处理尽可能安全和可读,排除“隐藏”错误,提升代码的可靠性,而不需要使用异常。
解决方案:Result<T, E>是一个具有两个变体的枚举:在成功时为Ok(T),在出错时为Err(E)。这迫使显式处理错误,或者通过unwrap显式忽略,或者期待panic。此外,?运算符使得错误传播的常见模式变得简洁。
代码示例:
use std::fs::File; use std::io::{self, Read}; fn read_file(path: &str) -> Result<String, io::Error> { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
关键特性:
?运算符简单地链式传播错误。可以随时使用?运算符将错误自动传递到上层吗?
不可以,只有当函数的错误类型与?右侧表达式的类型匹配时才能使用。如果类型不兼容,需要通过.map_err()方法或自定义类型进行显式转换。
代码示例:
fn f() -> Result<(), String> { let _f = File::open("foo").map_err(|e| e.to_string())?; Ok(()) }
如果不关心错误,是否可以留存Result的结果未使用?
不可以,编译器会发出警告或错误,如果Result类型的结果未处理。要么调用.unwrap(),要么明确调用.ok()/.err()/let _ = ...或者正确记录错误。
如果在包含错误的Result上调用.unwrap()会发生什么?
将会引发panic!并中止程序的执行,这通常会导致程序崩溃。因此,当成功是有保障时,才允许使用unwrap()。
let _ = ...;)而不进行记录?时错误类型不匹配,导致难以理解的编译错误开发者决定从文件中读取配置,并在读取结果上使用了unwrap()。
优点:
缺点:
读取配置时的错误记录并通过Result + ?运算符向上返回调用栈。
优点:
缺点: