Rust 错误处理与测试:打造健壮可维护应用的核心实践
Rust 错误处理机制基于 Result 类型,要求显式处理所有可能错误。文章详解 Result 变体、? 运算符原理及自定义错误类型实现,包括 Error trait 和 Display trait。测试体系涵盖单元测试、集成测试及文档测试,通过密码验证库和 CSV 解析器案例展示实战应用。强调避免 unwrap 滥用,构建完整错误链,确保生产环境代码的可靠性与维护性。

Rust 错误处理机制基于 Result 类型,要求显式处理所有可能错误。文章详解 Result 变体、? 运算符原理及自定义错误类型实现,包括 Error trait 和 Display trait。测试体系涵盖单元测试、集成测试及文档测试,通过密码验证库和 CSV 解析器案例展示实战应用。强调避免 unwrap 滥用,构建完整错误链,确保生产环境代码的可靠性与维护性。

? 运算符、match 表达式、if let 对错误进行处理与传播? 运算符的原理,掌握自定义类型对 ? 运算符的支持,遵循错误处理的最佳实践三大核心难点:
三大高频错误点:
Rust 的错误处理机制是显式的,它要求开发者必须处理所有可能的错误,避免了 C 语言中常见的空指针异常和未定义行为。
Result 类型是 Rust 中最常用的错误处理类型,它有两个变体:
Result 类型的基本操作示例:
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
// 读取文件内容的函数,返回 Result<String, Box<dyn std::error::Error>>
fn read_file_content(file_path: PathBuf) -> Result<String, Box<dyn std::error::Error>> {
// 尝试打开文件(返回 Result<File, io::Error>)
let mut file = File::open(&file_path)?;
// 使用?运算符传播错误
let mut content = String::new();
// 尝试读取文件内容(返回 Result<usize, io::Error>)
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
let file_path = PathBuf::from("nonexistent.txt");
// 方法 1:使用 match 表达式处理 Result
match read_file_content(file_path.clone()) {
Ok(content) => println!("文件内容:{}", content),
Err(e) => println!("错误 1: {}", e),
}
// 方法 2:使用 if let 处理 Result
if let Ok(content) = read_file_content(file_path.clone()) {
println!("文件内容:{}", content);
} else {
println!("错误 2: 文件不存在或无法访问");
}
// 方法 3:使用 unwrap() 或 expect()(生产环境不推荐)
// let content = read_file_content(file_path).unwrap(); // panic: 线程'主'已惊慌失措
let content = read_file_content(file_path).expect("读取文件失败");
}
?运算符是 Rust 中错误传播的简化语法,它的作用是:如果 Result 是 Ok(T),就返回 T;如果 Result 是 Err(E),就将 E 转换为函数返回类型的错误类型,并提前返回函数。
注意事项:
为了提供友好的错误信息和更好的错误处理体验,我们通常需要定义自己的错误类型。
自定义错误类型需要实现以下几个 trait:
自定义 HTTP 请求错误类型示例:
use std::error::Error;
use std::fmt;
use reqwest::Error as ReqwestError;
use serde_json::Error as SerdeJsonError;
// 定义自定义 HTTP 请求错误类型
#[derive(Debug)]
enum HttpRequestError {
// 网络错误(包含 reqwest 库的错误)
NetworkError(ReqwestError),
// JSON 序列化/反序列化错误(包含 serde_json 库的错误)
JsonError(SerdeJsonError),
// HTTP 状态码错误(包含状态码)
StatusCodeError(u16),
// 其他错误(包含错误信息)
OtherError(String),
}
// 实现 Display trait
impl fmt::Display for HttpRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HttpRequestError::NetworkError(e) => write!(f, "网络错误:{}", e),
HttpRequestError::JsonError(e) => write!(f, "JSON 序列化/反序列化错误:{}", e),
HttpRequestError::StatusCodeError(status) => write!(f, "HTTP 状态码错误:{}", status),
HttpRequestError::OtherError(msg) => write!(f, "其他错误:{}", msg),
}
}
}
// 实现 Error trait
impl Error for HttpRequestError {
// 实现 source 方法,用于构建错误链
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
HttpRequestError::NetworkError(e) => Some(e),
HttpRequestError::JsonError(e) => Some(e),
_ => None,
}
}
}
// 实现 From trait,用于将 ReqwestError 转换为 HttpRequestError
impl From<ReqwestError> for HttpRequestError {
fn from(e: ReqwestError) -> Self {
HttpRequestError::NetworkError(e)
}
}
// 实现 From trait,用于将 SerdeJsonError 转换为 HttpRequestError
impl From<SerdeJsonError> for HttpRequestError {
fn from(e: SerdeJsonError) -> Self {
HttpRequestError::JsonError(e)
}
}
// 发送 HTTP GET 请求的函数,返回 Result<serde_json::Value, HttpRequestError>
async fn send_http_get_request(url: &str) -> Result<serde_json::Value, HttpRequestError> {
let client = reqwest::Client::new();
let response = client.get(url).send().await?;
if response.status().is_success() {
let json = response.json().await?;
Ok(json)
} else {
Err(HttpRequestError::StatusCodeError(response.status().as_u16()))
}
}
#[tokio::main]
async fn main() {
let url = "https://jsonplaceholder.typicode.com/todos/1";
// 打印完整的错误链
if let Err(e) = send_http_get_request(url).await {
println!("错误:{}", e);
if let Some(source) = e.source() {
println!("错误来源:{}", source);
}
println!("调试信息:{:?}", e);
}
let invalid_url = "invalid_url";
if let Err(e) = send_http_get_request(invalid_url).await {
println!("错误:{}", e);
if let Some(source) = e.source() {
println!("错误来源:{}", source);
}
println!("调试信息:{:?}", e);
}
}
单元测试是 Rust 中验证代码正确性的基本方法,它测试函数或方法的输入输出是否符合预期。
测试函数需要满足以下条件:
数学库函数的单元测试示例:
// 数学库函数
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("除数不能为 0".to_string())
} else {
Ok(a / b)
}
}
// 单元测试模块(使用#[cfg(test)]宏标记,只在测试模式下编译)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
assert_eq!(add(-1, 5), 4);
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_subtract() {
assert_eq!(subtract(5, 2), 3);
assert_eq!(subtract(-1, 5), -6);
assert_eq!(subtract(0, 0), 0);
}
#[test]
fn test_multiply() {
assert_eq!(multiply(2, 3), 6);
assert_eq!(multiply(-1, 5), -5);
assert_eq!(multiply(0, 5), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(6, 2), Ok(3));
assert_eq!(divide(-6, 3), Ok(-2));
}
#[test]
#[should_panic(expected = "除数不能为 0")]
// 期望测试函数 panic,并包含指定的错误信息
fn test_divide_panic() {
divide(6, 0).unwrap();
}
#[test]
#[ignore]
// 忽略该测试函数(不执行)
fn test_ignore() {
assert_eq!(1, 2); // 该断言会失败,但测试会被忽略
}
}
在项目根目录下运行以下命令来执行单元测试:
cargo test
运行指定测试函数:
cargo test test_add
运行包含指定前缀的测试函数:
cargo test test_
运行所有未被忽略的测试函数:
cargo test -- --ignored
集成测试是 Rust 中验证代码模块之间协作正确性的方法,它测试整个库或应用程序的功能。
集成测试的代码通常放在项目根目录下的tests/目录中,每个测试文件都会被编译成一个单独的二进制文件。
HTTP 客户端请求工具的集成测试示例: 首先,在 src/lib.rs 中定义 HTTP 客户端请求工具的库函数:
use reqwest::Client;
use serde_json::Value;
pub enum HttpMethod {
GET,
POST,
}
pub struct HttpRequest {
pub url: String,
pub method: HttpMethod,
pub json_body: Option<Value>,
}
impl HttpRequest {
pub fn new(url: String, method: HttpMethod, json_body: Option<Value>) -> Self {
HttpRequest {
url,
method,
json_body,
}
}
pub async fn send(&self) -> Result<Value, Box<dyn std::error::Error>> {
let client = Client::new();
let response = match self.method {
HttpMethod::GET => client.get(&self.url).send().await?,
HttpMethod::POST => {
if let Some(body) = &self.json_body {
client.post(&self.url).json(body).send().await?
} else {
return Err("POST 请求需要提供 JSON body".into());
}
}
};
if response.status().is_success() {
let json = response.json().await?;
Ok(json)
} else {
Err(format!("HTTP 状态码错误:{}", response.status()).into())
}
}
}
然后,在 tests/integration_test.rs 中编写集成测试:
use http_client_request_tool::{HttpRequest, HttpMethod};
use serde_json::json;
#[tokio::test]
// 使用 tokio 的 test 宏,因为 HTTP 请求是异步的
async fn test_send_http_get_request() {
let url = "https://jsonplaceholder.typicode.com/todos/1";
let request = HttpRequest::new(url.to_string(), HttpMethod::GET, None);
let response = request.send().await.unwrap();
assert_eq!(response["userId"], 1);
assert_eq!(response["id"], 1);
assert_eq!(response["title"], "delectus aut autem");
assert_eq!(response["completed"], false);
}
#[tokio::test]
async fn test_send_http_post_request() {
let url = "https://jsonplaceholder.typicode.com/todos";
let json_body = json!({"userId": 1, "title": "test post request", "completed": false});
let request = HttpRequest::new(url.to_string(), HttpMethod::POST, Some(json_body));
let response = request.send().await.unwrap();
assert_eq!(response["userId"], 1);
assert_eq!(response["title"], "test post request");
assert_eq!(response["completed"], false);
assert!(response["id"].is_number());
}
#[tokio::test]
async fn test_send_http_request_with_invalid_url() {
let url = "invalid_url";
let request = HttpRequest::new(url.to_string(), HttpMethod::GET, None);
let result = request.send().await;
assert!(result.is_err());
if let Err(e) = result {
println!("错误信息:{}", e);
}
}
在项目根目录下运行以下命令来执行集成测试:
cargo test
文档测试是 Rust 中验证文档示例正确性的方法,它测试 ///注释中的代码块是否能够正常编译和运行。
文档测试的语法是在 ///注释中添加代码块,代码块的语法是:
/// ```
/// // 代码示例
/// ```
数学库函数的文档测试示例:
/// 计算两个整数的和
///
/// # 例子
///
/// ```
/// use math_lib::add;
///
/// let result = add(1, 2);
/// assert_eq!(result, 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 计算两个整数的差
///
/// # 例子
///
/// ```
/// use math_lib::subtract;
///
/// let result = subtract(5, 2);
/// assert_eq!(result, 3);
/// ```
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
/// 计算两个整数的积
///
/// # 例子
///
/// ```
/// use math_lib::multiply;
///
/// let result = multiply(2, 3);
/// assert_eq!(result, 6);
/// ```
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
/// 计算两个整数的商
///
/// # 例子
///
/// ```
/// use math_lib::divide;
///
/// let result = divide(6, 2);
/// assert_eq!(result, Ok(3));
/// ```
///
/// # 错误示例
///
/// ```
/// use math_lib::divide;
///
/// let result = divide(6, 0);
/// assert!(result.is_err());
/// ```
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("除数不能为 0".to_string())
} else {
Ok(a / b)
}
}
在项目根目录下运行以下命令来执行文档测试:
cargo test --doc
场景分析:需要编写一个安全的密码验证库,支持验证密码的长度、复杂度(包含大写字母、小写字母、数字、特殊字符),返回详细的错误信息,并包含完整的测试用例。
代码示例:
use std::error::Error;
use std::fmt;
// 定义密码验证错误类型
#[derive(Debug)]
enum PasswordValidationError {
// 密码长度不足(包含最小长度)
LengthTooShort(usize),
// 密码长度过长(包含最大长度)
LengthTooLong(usize),
// 密码不包含大写字母
NoUppercaseLetter,
// 密码不包含小写字母
NoLowercaseLetter,
// 密码不包含数字
NoDigit,
// 密码不包含特殊字符
NoSpecialCharacter,
}
// 实现 Display trait
impl fmt::Display for PasswordValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PasswordValidationError::LengthTooShort(min) => write!(f, "密码长度不足,至少需要{}个字符", min),
PasswordValidationError::LengthTooLong(max) => write!(f, "密码长度过长,最多需要{}个字符", max),
PasswordValidationError::NoUppercaseLetter => write!(f, "密码必须包含至少一个大写字母"),
PasswordValidationError::NoLowercaseLetter => write!(f, "密码必须包含至少一个小写字母"),
PasswordValidationError::NoDigit => write!(f, "密码必须包含至少一个数字"),
PasswordValidationError::NoSpecialCharacter => write!(f, "密码必须包含至少一个特殊字符(!@#$%^&*()_+-=[]{}|;:,.<>?)"),
}
}
}
// 实现 Error trait
impl Error for PasswordValidationError {}
// 密码验证配置
struct PasswordValidationConfig {
min_length: usize,
max_length: usize,
require_uppercase: bool,
require_lowercase: bool,
require_digit: bool,
require_special: bool,
}
impl Default for PasswordValidationConfig {
fn default() -> Self {
PasswordValidationConfig {
min_length: 8,
max_length: 32,
require_uppercase: true,
require_lowercase: true,
require_digit: true,
require_special: true,
}
}
}
impl PasswordValidationConfig {
fn new() -> Self {
Self::default()
}
fn with_min_length(mut self, min_length: usize) -> Self {
self.min_length = min_length;
self
}
fn with_max_length(mut self, max_length: usize) -> Self {
self.max_length = max_length;
self
}
fn without_uppercase(mut self) -> Self {
self.require_uppercase = false;
self
}
fn without_lowercase(mut self) -> Self {
self.require_lowercase = false;
self
}
fn without_digit(mut self) -> Self {
self.require_digit = false;
self
}
fn without_special(mut self) -> Self {
self.require_special = false;
self
}
}
// 密码验证函数
fn validate_password(password: &str, config: &PasswordValidationConfig) -> Result<(), PasswordValidationError> {
// 验证密码长度
if password.len() < config.min_length {
return Err(PasswordValidationError::LengthTooShort(config.min_length));
}
if password.len() > config.max_length {
return Err(PasswordValidationError::LengthTooLong(config.max_length));
}
// 验证密码复杂度
if config.require_uppercase && !password.chars().any(|c| c.is_uppercase()) {
return Err(PasswordValidationError::NoUppercaseLetter);
}
if config.require_lowercase && !password.chars().any(|c| c.is_lowercase()) {
return Err(PasswordValidationError::NoLowercaseLetter);
}
if config.require_digit && !password.chars().any(|c| c.is_numeric()) {
return Err(PasswordValidationError::NoDigit);
}
if config.require_special && !password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?".contains(c)) {
return Err(PasswordValidationError::NoSpecialCharacter);
}
Ok(())
}
// 单元测试模块
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_password_with_default_config_success() {
let password = "P@ssw0rd123";
let config = PasswordValidationConfig::new();
assert!(validate_password(password, &config).is_ok());
}
#[test]
fn test_validate_password_with_custom_config_success() {
let password = "passw0rd";
let config = PasswordValidationConfig::new().without_uppercase().without_special();
assert!(validate_password(password, &config).is_ok());
}
#[test]
fn test_validate_password_length_too_short() {
let password = "P@ssw0r";
let config = PasswordValidationConfig::new();
let result = validate_password(password, &config);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, PasswordValidationError::LengthTooShort(8)));
}
}
#[test]
fn test_validate_password_no_uppercase() {
let password = "p@ssw0rd123";
let config = PasswordValidationConfig::new();
let result = validate_password(password, &config);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, PasswordValidationError::NoUppercaseLetter));
}
}
#[test]
fn test_validate_password_no_special() {
let password = "Password123";
let config = PasswordValidationConfig::new();
let result = validate_password(password, &config);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, PasswordValidationError::NoSpecialCharacter));
}
}
}
fn main() {
let password = "P@ssw0rd123";
let config = PasswordValidationConfig::new();
match validate_password(password, &config) {
Ok(_) => println!("密码验证成功"),
Err(e) => println!("密码验证失败:{}", e),
}
}
场景分析:需要编写一个简单的 CSV 解析器,支持解析 CSV 文件和字符串,返回一个二维数组,处理解析过程中可能出现的错误,并包含完整的测试用例。
代码示例:
use std::error::Error;
use std::fmt;
use std::fs::File;
use std::io::{Read, BufRead, BufReader};
use std::path::PathBuf;
// 定义 CSV 解析错误类型
#[derive(Debug)]
enum CsvParseError {
// 文件操作错误(包含 io::Error)
FileError(std::io::Error),
// CSV 行格式错误(包含行号和错误信息)
LineFormatError(usize, String),
// CSV 列数不一致错误(包含期望列数和实际列数)
ColumnCountError(usize, usize),
}
// 实现 Display trait
impl fmt::Display for CsvParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CsvParseError::FileError(e) => write!(f, "文件操作错误:{}", e),
CsvParseError::LineFormatError(line, msg) => write!(f, "第{}行格式错误:{}", line, msg),
CsvParseError::ColumnCountError(expected, actual) => write!(f, "列数不一致错误:期望{}列,实际{}列", expected, actual),
}
}
}
// 实现 Error trait
impl Error for CsvParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CsvParseError::FileError(e) => Some(e),
_ => None,
}
}
}
// 实现 From trait,用于将 io::Error 转换为 CsvParseError
impl From<std::io::Error> for CsvParseError {
fn from(e: std::io::Error) -> Self {
CsvParseError::FileError(e)
}
}
// CSV 解析配置
struct CsvParseConfig {
delimiter: char,
has_header: bool,
}
impl Default for CsvParseConfig {
fn default() -> Self {
CsvParseConfig {
delimiter: ',',
has_header: false,
}
}
}
impl CsvParseConfig {
fn new() -> Self {
Self::default()
}
fn with_delimiter(mut self, delimiter: char) -> Self {
self.delimiter = delimiter;
self
}
fn with_header(mut self) -> Self {
self.has_header = true;
self
}
}
// CSV 解析函数
fn parse_csv_string(csv: &str, config: &CsvParseConfig) -> Result<Vec<Vec<String>>, CsvParseError> {
let mut lines = csv.lines();
let mut result = Vec::new();
let mut expected_columns = None;
let mut line_number = 0;
// 跳过表头
if config.has_header {
if let Some(line) = lines.next() {
line_number += 1;
let row = parse_csv_line(line, config.delimiter, line_number)?;
expected_columns = Some(row.len());
result.push(row);
}
}
// 解析数据行
while let Some(line) = lines.next() {
line_number += 1;
let row = parse_csv_line(line, config.delimiter, line_number)?;
// 验证列数是否一致
if let Some(expected) = expected_columns {
if row.len() != expected {
return Err(CsvParseError::ColumnCountError(expected, row.len()));
}
} else {
expected_columns = Some(row.len());
}
result.push(row);
}
Ok(result)
}
// 解析 CSV 行的辅助函数
fn parse_csv_line(line: &str, delimiter: char, line_number: usize) -> Result<Vec<String>, CsvParseError> {
let mut row = Vec::new();
let mut current_cell = String::new();
let mut in_quote = false;
for c in line.chars() {
match c {
'"' => in_quote = !in_quote,
d if d == delimiter && !in_quote => {
row.push(current_cell.trim().to_string());
current_cell.clear();
}
_ => current_cell.push(c),
}
}
row.push(current_cell.trim().to_string());
if in_quote {
return Err(CsvParseError::LineFormatError(line_number, "引号未闭合".to_string()));
}
Ok(row)
}
// 解析 CSV 文件的函数
fn parse_csv_file(file_path: PathBuf, config: &CsvParseConfig) -> Result<Vec<Vec<String>>, CsvParseError> {
let file = File::open(file_path)?;
let reader = BufReader::new(file);
let mut csv = String::new();
for line_result in reader.lines() {
let line = line_result?;
csv.push_str(&line);
csv.push('\n');
}
parse_csv_string(&csv, config)
}
// 单元测试模块
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_csv_string_with_default_config() {
let csv = "1,张三,18\n2,李四,20\n3,王五,22";
let config = CsvParseConfig::new();
let result = parse_csv_string(csv, &config).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0], vec!["1", "张三", "18"]);
assert_eq!(result[1], vec!["2", "李四", "20"]);
assert_eq!(result[2], vec!["3", "王五", "22"]);
}
#[test]
fn test_parse_csv_string_with_header() {
let csv = "ID,姓名,年龄\n1,张三,18\n2,李四,20\n3,王五,22";
let config = CsvParseConfig::new().with_header();
let result = parse_csv_string(csv, &config).unwrap();
assert_eq!(result.len(), 4);
assert_eq!(result[0], vec!["ID", "姓名", "年龄"]);
assert_eq!(result[1], vec!["1", "张三", "18"]);
}
#[test]
fn test_parse_csv_string_with_semicolon_delimiter() {
let csv = "1;张三;18\n2;李四;20\n3;王五;22";
let config = CsvParseConfig::new().with_delimiter(';');
let result = parse_csv_string(csv, &config).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0], vec!["1", "张三", "18"]);
}
#[test]
fn test_parse_csv_string_with_quoted_cell() {
let csv = "1,\"张三,李四\",18\n2,王五,20";
let config = CsvParseConfig::new();
let result = parse_csv_string(csv, &config).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], vec!["1", "张三,李四", "18"]);
}
#[test]
fn test_parse_csv_string_column_count_error() {
let csv = "1,张三,18\n2,李四\n3,王五,22";
let config = CsvParseConfig::new();
let result = parse_csv_string(csv, &config);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, CsvParseError::ColumnCountError(3, 2)));
}
}
#[test]
fn test_parse_csv_string_quote_error() {
let csv = "1,\"张三,李四,18\n2,王五,20";
let config = CsvParseConfig::new();
let result = parse_csv_string(csv, &config);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, CsvParseError::LineFormatError(1, _)));
}
}
}
fn main() {
let csv = "ID,姓名,年龄\n1,张三,18\n2,李四,20\n3,王五,22";
let config = CsvParseConfig::new().with_header();
match parse_csv_string(csv, &config) {
Ok(result) => {
println!("CSV 解析成功");
for row in result {
println!("{:?}", row);
}
}
Err(e) => println!("CSV 解析失败:{}", e),
}
}
问题现象:在不返回 Result 类型的函数中使用 ? 运算符,导致编译错误。
解决方案:
问题现象:assert_eq! 或 assert_ne! 的左右操作数类型不一致,导致测试失败。
解决方案:
问题现象:使用 unwrap() 或 expect() 处理所有错误,在生产环境中容易导致程序崩溃。
解决方案:
✅ 掌握了错误处理基础:理解了 Result 类型的核心作用,熟练运用了 ? 运算符、match 表达式、if let 对错误进行处理与传播
✅ 精通了自定义错误类型:深入学习了 std::error::Error trait 的实现方法,构建了完整的错误链,提供了友好的错误信息
✅ 优化了错误传播方式:了解了 ? 运算符的原理,掌握了自定义类型对 ? 运算符的支持,遵循了错误处理的最佳实践
✅ 完善了测试体系:熟练编写了单元测试(#[test] 宏)、集成测试(tests/目录)、文档测试(///注释代码块)
✅ 实战测试开发:结合真实场景编写了两个实用的代码案例:安全的密码验证库和简单的 CSV 解析器,包含详细的错误处理和完整的测试用例
下一篇文章,我们将深入学习 Rust 的异步编程进阶,包括 Tokio 库的任务调度、流的处理、超时设置、连接池,通过这些知识我们将能够编写更高效的异步应用程序,如 Web 服务器、客户端、数据处理工具。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online