Rust 错误处理高级应用指南
1. 错误处理基础
Rust 的错误处理是通过 Result 类型和 panic! 宏实现的。
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
}
2. 高级错误处理技巧
2.1 自定义错误类型
use std::fmt;
#[derive(Debug)]
enum MyError {
DivisionByZero,
NegativeNumber,
Custom(String),
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::DivisionByZero => write!(f, "Division by zero"),
MyError::NegativeNumber => write!(f, "Negative number"),
MyError::Custom(msg) => write!(f, "Custom error: {}", msg),
}
}
}
impl std::error::Error for MyError {}
fn divide(a: f64, b: f64) -> Result<f64, MyError> {
if b == 0.0 {
Err(MyError::DivisionByZero)
} else if a < 0.0 || b < 0.0 {
Err(MyError::NegativeNumber)
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
}
2.2 使用 ? 操作符
use std::fs::File;
use std::io::Read;
fn read_file(filename: &str) -> Result<String, std::io::Error> {
let mut file = File::open(filename)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file("input.txt") {
Ok(content) => println!("File content: {}", content),
Err(error) => println!("Error: {}", error),
}
}
2.3 错误链
use std::error::Error;
use std::fs::File;
use std::io::Read;
fn read_file(filename: &str) -> Result<String, Box<dyn Error>> {
let mut file = File::open(filename)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file("input.txt") {
Ok(content) => println!("File content: {}", content),
Err(error) => {
println!("Error: {}", error);
let mut current = Some(error.as_ref());
while let Some(err) = current {
if let Some(source) = err.source() {
println!("Caused by: {}", source);
current = Some(source);
} else {
current = None;
}
}
}
}
}
2.4 使用 anyhow 库
use anyhow::{Context, Result};
use std::fs::File;
use std::io::Read;
fn read_file(filename: &str) -> Result<String> {
let mut file = File::open(filename).context(format!("Failed to open file: {}", filename))?;
let mut content = String::new();
file.read_to_string(&mut content).context("Failed to read file")?;
Ok(content)
}
fn main() {
match read_file("input.txt") {
Ok(content) => println!("File content: {}", content),
Err(error) => println!("Error: {:#}", error),
}
}
3. 实际应用场景
3.1 网络请求
use anyhow::{Context, Result};
use reqwest::Client;
async fn fetch_url(url: &str) -> Result<String> {
let client = Client::new();
let response = client.get(url)
.send()
.await
.context(format!("Failed to send request to {}", url))?;
let content = response.text()
.await
.context("Failed to read response")?;
Ok(content)
}
#[tokio::main]
async fn main() {
match fetch_url("https://api.github.com").await {
Ok(content) => println!("Content length: {}", content.len()),
Err(error) => println!("Error: {:#}", error),
}
}
3.2 文件 I/O
use anyhow::{Context, Result};
use std::fs::File;
use std::io::{Read, Write};
fn read_file(filename: &str) -> Result<String> {
let mut file = File::open(filename)
.context(format!("Failed to open file: {}", filename))?;
let mut content = String::new();
file.read_to_string(&mut content)
.context("Failed to read file")?;
Ok(content)
}
fn write_file(filename: &str, content: &str) -> Result<()> {
let mut file = File::create(filename)
.context(format!("Failed to create file: {}", filename))?;
file.write_all(content.as_bytes())
.context("Failed to write file")?;
Ok(())
}
fn main() {
match read_file("input.txt") {
Ok(content) => {
println!("Read content: {}", content);
if let Err(error) = write_file("output.txt", &content.to_uppercase()) {
println!("Error writing file: {:#}", error);
} else {
println!("File written successfully");
}
}
Err(error) => println!("Error reading file: {:#}", error),
}
}
3.3 数据库操作
use anyhow::{Context, Result};
use sqlx::postgres::PgPool;
async fn get_users(pool: &PgPool) -> Result<Vec<(i32, String)>> {
sqlx::query_as::<_, (i32, String)>("SELECT id, name FROM users")
.fetch_all(pool)
.await
.context("Failed to fetch users")
}
#[tokio::main]
async fn main() {
let pool = PgPool::connect("postgres://postgres:password@localhost/test")
.await
.context("Failed to connect to database")
.unwrap();
match get_users(&pool).await {
Ok(users) => {
for (id, name) in users {
println!("User: {} - {}", id, name);
}
}
Err(error) => println!("Error: {:#}", error),
}
}
3.4 命令行工具
use anyhow::{Context, Result};
use clap::Parser;
use std::fs::File;
use std::io::Read;
#[derive(Parser, Debug)]
#[clap(about, version, author)]
struct Args {
#[clap(short, long)]
filename: String,
}
fn read_file(filename: &str) -> Result<String> {
let mut file = File::open(filename)
.context(format!("Failed to open file: {}", filename))?;
let mut content = String::new();
file.read_to_string(&mut content)
.context("Failed to read file")?;
Ok(content)
}
fn main() {
let args = Args::parse();
match read_file(&args.filename) {
Ok(content) => println!("File content:\n{}", content),
Err(error) => {
eprintln!("Error: {:#}", error);
std::process::exit(1);
}
}
}
4. 最佳实践
- 使用
Result类型:对于可能失败的操作,使用Result类型返回错误。 - 使用
?操作符:使用?操作符简化错误传播。 - 自定义错误类型:对于特定领域的错误,自定义错误类型可以提供更清晰的错误信息。
- 使用错误链:使用错误链可以提供更详细的错误上下文。
- 使用
anyhow库:对于应用程序代码,使用anyhow库可以简化错误处理。 - 使用
thiserror库:对于库代码,使用thiserror库可以创建自定义错误类型。 - 处理错误:不要忽略错误,应该适当处理或向上传播。
5. 总结
Rust 的错误处理是一种强大的机制,它允许我们以一种类型安全、清晰的方式处理错误。通过掌握错误处理的高级应用,我们可以编写更加健壮、可靠的代码。
在实际应用中,错误处理可以用于网络请求、文件 I/O、数据库操作、命令行工具等多种场景,大大提高代码的可靠性和可维护性。
希望本文对你理解和应用 Rust 错误处理有所帮助!
2911



被折叠的 条评论
为什么被折叠?



