简介
有些失败属于编程错误,但另一些失败会在运行时正常发生:用户可能输入格式错误的文本,或者输入超出允许范围的值。Rust 使用 Result<T, E> 表示这类可恢复的结果。它的值只能是 Ok(T) 或 Err(E)。
你将学习如何在不触发 panic 的情况下解析有效和无效文本、返回有用的验证错误,并使用 ? 运算符将失败结果传递给调用者。预先准备好的 Cargo 项目会将每次修改都集中在错误处理上。
匹配成功的 Result
在本步骤中,你将把文本解析为整数,并匹配解析成功的结果。
项目位于 /home/labex/project/result-workshop,src/main.rs 中包含 4 个需要完成的占位位置。进入项目目录并打开源文件:
cd /home/labex/project/result-workshop
nano src/main.rs
将步骤 1 的注释替换为:
let valid = "42".parse::<i32>();
match valid {
Ok(value) => println!("Parsed value: {value}"),
Err(error) => println!("Parse error: {error}"),
}
parse 是一个可复用的方法,可以生成不同的输出类型。::<i32> 部分为这次调用指定输出类型,要求 Rust 将字符串解析为带符号的 32 位整数。这个尖括号类型参数不同于 Message::Text 这样的路径,尽管两者都使用 ::。解析可能失败,因此返回的类型是 Result。Ok(value) 包含解析后的整数,Err(error) 包含解析失败原因的信息。match 必须处理这两个变体。
使用 Ctrl+O 保存,按 Enter 确认,然后使用 Ctrl+X 退出。检查并运行程序:
cargo check
cargo run --quiet
Parsed value: 42
由于 "42" 是有效的整数文本,因此只有 Ok 分支执行了。
从无效输入中恢复
在本步骤中,你将把格式错误的文本作为数据处理,而不是让它导致程序崩溃。
panic 表示程序进入了设计中未预期的状态,通常会停止执行。外部输入格式错误是可以预料的情况,因此应保留为程序可以检查和报告的 Err。打开源文件:
nano src/main.rs
将步骤 2 的注释替换为:
let invalid = "many".parse::<i32>();
match invalid {
Ok(value) => println!("Unexpected value: {value}"),
Err(error) => println!("Handled invalid input: {error}"),
}
这个匹配仍然覆盖两种可能的结果,但预先准备的文本会选择 Err 分支。保存并退出,然后运行:
cargo run --quiet
第二行应为:
Handled invalid input: invalid digit found in string
程序正常继续执行,并将解析器返回的错误转换为有用的消息。处理外部数据时,应避免使用 unwrap():对于同一个 Err,unwrap() 会触发 panic,而不是让你恢复处理。
使用问号运算符解析和验证
在本步骤中,你将创建一个函数,使其能够返回百分比或有用的错误消息。
类型 Result<u32, String> 表示成功时包含一个无符号整数,失败时包含拥有所有权的错误文本。? 运算符会取出 Ok 中的值;如果遇到 Err,它会立即从当前函数返回该错误。
打开源文件:
nano src/main.rs
将以下函数添加到 main 之前:
fn parse_percentage(text: &str) -> Result<u32, String> {
let value = text
.parse::<u32>()
.map_err(|_| format!("not a whole number: {text}"))?;
if value <= 100 {
Ok(value)
} else {
Err(format!("outside 0..=100: {value}"))
}
}
可以把这个函数理解为一组简短的步骤。首先,parse::<u32>() 尝试将文本转换为整数。然后,map_err(...) 只在解析失败时修改错误,将其转换为包含原始输入的更清晰的 String。
接下来,? 要么取出解析成功的数字并赋值给 value,要么立即从 parse_percentage 返回新的错误。如果解析成功,if 会验证这个数字,并返回 Ok(value) 或表示超出范围的 Err。
闭包参数 _ 表示有意忽略原始解析器错误值,因为新的消息会替代它。这里的 _ 是一个被忽略的参数;前面,_ => 是通配匹配分支,而 let _ = 会丢弃完整的结果。
将 main 中步骤 3 的注释替换为:
println!("85 => {:?}", parse_percentage("85"));
println!("150 => {:?}", parse_percentage("150"));
println!("many => {:?}", parse_percentage("many"));
:? 格式化方式会显示 Ok 或 Err 变体,便于在本实验中观察结果。保存并退出,然后检查并运行:
cargo check
cargo run --quiet
新增的几行应为:
85 => Ok(85)
150 => Err("outside 0..=100: 150")
many => Err("not a whole number: many")
现在,这个函数可以区分有效值、已成功解析但超出范围的值,以及格式错误的文本。
通过另一个函数传播错误
在本步骤中,你将在第二个函数边界处使用 ?,并在 main 中处理最终结果。
辅助函数不一定知道应用程序应该如何显示错误或从错误中恢复。它可以向上层传播 Err,将决定权留给调用者。打开源文件:
nano src/main.rs
将以下函数添加到 parse_percentage 和 main 之间:
fn acceptance_message(text: &str) -> Result<String, String> {
let value = parse_percentage(text)?;
Ok(format!("accepted {value}%"))
}
如果解析或验证失败,? 会立即返回现有的 String 错误。成功时,函数会将消息封装到 Ok 中。
将 main 中步骤 4 的注释替换为:
for text in ["73", "bad"] {
match acceptance_message(text) {
Ok(message) => println!("{text} => {message}"),
Err(message) => println!("{text} => error: {message}"),
}
}
这个循环分别提供一个成功输入和一个失败输入。main 是将每个结果转换为面向用户输出的边界。保存并退出,然后检查并运行:
cargo check
cargo run --quiet
最后几行应为:
73 => accepted 73%
bad => error: not a whole number: bad
parse_percentage 创建的相同错误上下文,在经过 acceptance_message 传播后仍然得以保留。
总结
你已经学会匹配成功和失败的 Result 值,将格式错误的输入视为可恢复的情况,使用 map_err 创建包含上下文的 String 错误,并通过函数边界使用 ? 传播失败结果,而不是使用 unwrap 触发 panic。


