一、学习目标与重点
1.1 学习目标
- 掌握错误处理基础 :理解Result类型的核心作用,熟练运用
?运算符、match表达式、if let对错误进行处理与传播 - 精通自定义错误类型:深入学习std::error::Error trait的实现方法,构建完整的错误链,提供友好的错误信息
- 优化错误传播方式 :了解
?运算符的原理,掌握自定义类型对?运算符的支持,遵循错误处理的最佳实践 - 完善测试体系:熟练编写单元测试(#[test]宏)、集成测试(tests/目录)、文档测试(///注释代码块)
- 实战测试开发:结合真实场景编写密码验证库、CSV解析器,包含详细的错误处理和完整的测试用例
1.2 学习重点
💡 三大核心难点:
- 自定义错误类型的设计:如何根据业务需求定义包含足够信息的错误类型,如错误代码、错误信息、错误来源
- 错误链的构建与打印:如何实现source方法来连接多个错误,以及如何使用{:?}或{:#?}打印完整的错误链
- 测试用例的覆盖率:如何编写全面的测试用例,覆盖正常场景、边界条件、错误场景,提高代码的可靠性
⚠️ 三大高频错误点:
- ?运算符的误用:在不返回Result类型的函数中使用?运算符,导致编译错误
- 测试断言的类型不匹配:assert_eq!或assert_ne!的左右操作数类型不一致,导致测试失败
- 忽略错误导致的程序崩溃:使用unwrap()或expect()处理所有错误,在生产环境中容易导致程序崩溃
二、错误处理基础
Rust的错误处理机制是显式的,它要求开发者必须处理所有可能的错误,避免了C语言中常见的[空指针异常]和未定义行为。
2.1 Result类型的详解
Result类型是Rust中最常用的错误处理类型,它有两个变体:
- Ok(T):表示操作成功,包含成功值T
- Err(E):表示操作失败,包含错误值E
⌨️ Result类型的基本操作示例:
rust
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("读取文件失败");
}
AI写代码rust
运行
123456789101112131415161718192021222324252627282930313233343536
2.2 ?运算符的使用
?运算符是Rust中错误传播的简化语法,它的作用是:如果Result是Ok(T),就返回T;如果Result是Err(E),就将E转换为函数返回类型的错误类型,并提前返回函数。
⚠️ 注意事项:
- ?运算符只能用在返回Result类型的函数中
- ?运算符会自动调用From trait将E转换为函数返回类型的错误类型
- 如果函数返回类型是Result<T, E>,那么?运算符要求E必须实现From trait
三、自定义错误类型
为了提供友好的错误信息和更好的错误处理体验,我们通常需要定义自己的错误类型。
3.1 自定义错误类型的实现
自定义错误类型需要实现以下几个trait:
- std::error::Error:定义错误的通用接口,包含source方法(用于构建错误链)、description方法(已弃用,建议使用Display trait)
- std::fmt::Display:定义错误的字符串表示
- std::fmt::Debug:定义错误的调试表示
- 可选:std::convert::From trait:用于将其他类型的错误转换为自定义错误类型
⌨️ 自定义HTTP请求错误类型示例:
rust
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);
}
}
AI写代码rust
运行
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
四、单元测试
单元测试是Rust中验证代码正确性的基本方法,它测试函数或方法的输入输出是否符合预期。
4.1 测试函数的编写
测试函数需要满足以下条件:
- 使用#[test]宏标记
- 不接受参数,不返回值
- 可以使用测试断言函数(assert!、assert_eq!、assert_ne!)来验证结果
⌨️ 数学库函数的单元测试示例:
scss
// 数学库函数
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); // 该断言会失败,但测试会被忽略
}
}
AI写代码rust
运行
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
4.2 运行单元测试
在项目根目录下运行以下命令来执行单元测试:
bash
cargo test
AI写代码bash
1
运行指定测试函数:
bash
cargo test test_add
AI写代码bash
1
运行包含指定前缀的测试函数:
bash
cargo test test_
AI写代码bash
1
运行所有未被忽略的测试函数:
bash
cargo test -- --ignored
AI写代码bash
1
五、集成测试
集成测试是Rust中验证代码模块之间协作正确性的方法,它测试整个库或应用程序的功能。
5.1 集成测试的目录结构
集成测试的代码通常放在项目根目录下的tests/目录中,每个测试文件都会被编译成一个单独的二进制文件。
5.2 集成测试的编写
⌨️ HTTP客户端请求工具的集成测试示例 :
首先,在src/lib.rs中定义HTTP客户端请求工具的库函数:
rust
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())
}
}
}
AI写代码rust
运行
12345678910111213141516171819202122232425262728293031323334353637383940
然后,在tests/integration_test.rs中编写集成测试:
rust
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);
}
}
AI写代码rust
运行
12345678910111213141516171819202122232425262728293031323334353637383940414243
5.3 运行集成测试
在项目根目录下运行以下命令来执行集成测试:
bash
cargo test
AI写代码bash
1
六、文档测试
文档测试是Rust中验证文档示例正确性的方法,它测试///注释中的代码块是否能够正常编译和运行。
6.1 文档测试的语法
文档测试的语法是在///注释中添加代码块,代码块的语法是:
ruby
/// ```
/// // 代码示例
/// ```
AI写代码rust
运行
123
⌨️ 数学库函数的文档测试示例:
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)
}
}
AI写代码rust
运行
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
6.2 运行文档测试
在项目根目录下运行以下命令来执行文档测试:
bash
cargo test --doc
AI写代码bash
1
七、真实案例应用
7.1 案例1:实现安全的密码验证库
💡 场景分析:需要编写一个安全的密码验证库,支持验证密码的长度、复杂度(包含大写字母、小写字母、数字、特殊字符),返回详细的错误信息,并包含完整的测试用例。
⌨️ 代码示例:
rust
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),
}
}
AI写代码rust
运行
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
7.2 案例2:实现简单的CSV解析器
💡 场景分析:需要编写一个简单的CSV解析器,支持解析CSV文件和字符串,返回一个二维数组,处理解析过程中可能出现的错误,并包含完整的测试用例。
⌨️ 代码示例:
rust
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),
}
}
AI写代码rust
运行
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
八、常见问题与解决方案
8.1 ?运算符的误用
问题现象:在不返回Result类型的函数中使用?运算符,导致编译错误。
解决方案:
- 确保使用?运算符的函数返回Result类型
- 如果函数需要返回其他类型,可以使用match表达式或if let处理Result
- 在main函数中使用?运算符,需要将main函数的返回类型改为Result<(), Box>
8.2 测试断言的类型不匹配
问题现象:assert_eq!或assert_ne!的左右操作数类型不一致,导致测试失败。
解决方案:
- 检查左右操作数的类型是否一致
- 如果类型不一致,可以使用类型转换函数(如as u32)
- 使用{:?}打印调试信息,查看类型不匹配的原因
8.3 忽略错误导致的程序崩溃
问题现象:使用unwrap()或expect()处理所有错误,在生产环境中容易导致程序崩溃。
解决方案:
- 对于生产环境中的代码,应该使用match表达式或if let处理所有错误
- 提供友好的错误信息,帮助用户定位问题
- 使用日志库(如log、env_logger)记录错误信息
九、总结与展望
9.1 总结
✅ 掌握了错误处理基础 :理解了Result类型的核心作用,熟练运用了?运算符、match表达式、if let对错误进行处理与传播
✅ 精通了自定义错误类型 :深入学习了std::error::Error trait的实现方法,构建了完整的错误链,提供了友好的错误信息
✅ 优化了错误传播方式 :了解了?运算符的原理,掌握了自定义类型对?运算符的支持,遵循了错误处理的最佳实践
✅ 完善了测试体系 :熟练编写了单元测试(#[test]宏)、集成测试(tests/目录)、文档测试(///注释代码块)
✅ 实战测试开发:结合真实场景编写了两个实用的代码案例:安全的密码验证库和简单的CSV解析器,包含详细的错误处理和完整的测试用例
9.2 展望
下一篇文章,我们将深入学习Rust的异步编程进阶,包括Tokio库的任务调度、流的处理、超时设置、连接池,通过这些知识我们将能够编写更高效的异步应用程序,如Web服务器、客户端、数据处理工具。