第25章:最佳实践
第25章:最佳实践
25.1 代码风格
rustfmt 格式化
# 格式化整个项目
cargo fmt
# 检查格式(不修改)
cargo fmt -- --check
# 格式化单个文件
rustfmt src/main.rs
rustfmt.toml 配置
# rustfmt.toml
edition = "2024"
max_width = 100
tab_spaces = 4
use_field_init_shorthand = true
use_try_shorthand = true
命名规范
| 类型 | 风格 | 示例 |
|---|
| 变量/函数 | snake_case | my_function, count |
| 类型/Trait | PascalCase | MyStruct, Display |
| 常量 | SCREAMING_SNAKE_CASE | MAX_SIZE, PI |
| 生命周期 | 短小写 | 'a, 'de, 'ctx |
| 模块 | snake_case | my_module, config |
| Crate | snake_case | serde_json, tokio |
25.2 Clippy 静态分析
基本用法
# 运行 clippy
cargo clippy
# 更严格的检查
cargo clippy -- -W clippy::all -W clippy::pedantic
# CI 中使用(任何警告都失败)
cargo clippy -- -D warnings
Clippy 配置
# Cargo.toml
[lints.clippy]
enum_glob_use = "deny"
pedantic = { level = "warn", priority = -1 }
或在代码中:
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::must_use_candidate)]
fn main() {
// clippy 会提示可以改进的代码
}
常见 Clippy 提示
| 警告 | 说明 | 修复 |
|---|
needless_return | 不必要的 return | 删除 return |
redundant_clone | 不必要的 clone | 使用引用 |
single_match | 只有一个分支的 match | 使用 if let |
manual_map | 可以用 map 替代 | 使用 .map() |
needless_borrow | 不必要的借用 | 删除 & |
cast_possible_truncation | 类型转换可能截断 | 使用 try_into() |
module_name_repetitions | 模块名重复 | 调整命名 |
25.3 性能优化
编译优化
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
panic = "abort"
# 开发模式也开启部分优化
[profile.dev]
opt-level = 1
运行时优化
use std::collections::HashMap;
fn main() {
// ✅ 使用 with_capacity 预分配
let mut v = Vec::with_capacity(1000);
let mut map = HashMap::with_capacity(100);
// ✅ 使用迭代器而非循环(编译器更容易优化)
let sum: i32 = (1..=1000).filter(|x| x % 2 == 0).map(|x| x * x).sum();
println!("偶数平方和: {}", sum);
// ✅ 避免不必要的分配
let data = "hello world";
let len = data.len(); // 不需要 String
println!("长度: {}", len);
// ✅ 使用 &str 而非 String 作为参数
fn process(input: &str) -> usize {
input.len()
}
println!("{}", process("test"));
// ✅ 使用 Cow<str> 处理可能需要拥有的字符串
use std::borrow::Cow;
fn maybe_owned(input: &str) -> Cow<str> {
if input.contains(" ") {
Cow::Owned(input.replace(" ", "_"))
} else {
Cow::Borrowed(input)
}
}
println!("{:?}", maybe_owned("hello world"));
println!("{:?}", maybe_owned("hello"));
}
避免不必要的 Clone
fn main() {
let name = String::from("Alice");
// ❌ 不必要的 clone
// let greeting = format!("Hello, {}!", name.clone());
// ✅ 使用引用
let greeting = format!("Hello, {}!", name);
println!("{}", greeting);
// ✅ 使用 as_str()
let s: &str = &name; // 自动解引用
println!("{}", s);
// ✅ 必须拥有所有权时才 clone
let owned: String = name.clone();
println!("{}", owned);
}
25.4 常见陷阱
陷阱1:整数溢出
fn main() {
let a: u8 = 255;
// Debug 模式 panic,Release 模式回绕
// let b = a + 1; // Debug: panic
// ✅ 使用 checked_* 方法
let b = a.checked_add(1);
println!("checked_add: {:?}", b); // None
// ✅ 使用 saturating_* 方法(饱和)
let c = a.saturating_add(1);
println!("saturating_add: {}", c); // 255
// ✅ 使用 wrapping_* 方法(明确回绕)
let d = a.wrapping_add(1);
println!("wrapping_add: {}", d); // 0
}
陷阱2:浮点数比较
fn main() {
let a: f64 = 0.1 + 0.2;
let b: f64 = 0.3;
// ❌ 直接比较浮点数
// println!("{}", a == b); // 可能是 false
// ✅ 使用近似比较
let epsilon = f64::EPSILON;
println!("近似相等: {}", (a - b).abs() < epsilon);
// ✅ 或使用 approximate crate
println!("0.1 + 0.2 ≈ 0.3: {}", (0.1_f64 + 0.2 - 0.3).abs() < 1e-10);
}
陷阱3:闭包捕获所有权
fn main() {
let v = vec![1, 2, 3];
// ❌ move 闭包夺走所有权
// let closure = move || println!("{:?}", v);
// println!("{:?}", v); // 错误:v 已移动
// ✅ 如果只需要引用,不使用 move
let closure = || println!("{:?}", v);
closure();
println!("{:?}", v); // ✅ v 仍然有效
// ✅ 如果需要 move,先 clone
let v2 = v.clone();
let closure = move || println!("{:?}", v2);
closure();
println!("{:?}", v); // ✅
}
陷阱4:生命周期省略失败
// ❌ 编译器无法推断生命周期
// fn first_or(s: &str, default: &str) -> &str {
// if s.is_empty() { default } else { s }
// }
// ✅ 添加生命周期标注
fn first_or<'a>(s: &'a str, default: &'a str) -> &'a str {
if s.is_empty() { default } else { s }
}
fn main() {
let result = first_or("", "default");
println!("{}", result);
}
陷阱5:HashMap 键的类型
use std::collections::HashMap;
fn main() {
// ❌ 使用 String 作为键(需要克隆或转换)
let mut map: HashMap<String, i32> = HashMap::new();
map.insert("key".to_string(), 42);
let value = map.get("key"); // 需要 &String
// ✅ 使用 &str 作为键(如果生命周期允许)
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("key", 42);
let value = map.get("key"); // ✅ 自动转换
println!("{:?}", value);
}
陷阱6:Option/Result 误用 unwrap
fn main() {
let data = vec![1, 2, 3];
// ❌ 使用 unwrap(如果为空会 panic)
// let first = data.first().unwrap();
// ✅ 使用 unwrap_or / unwrap_or_default
let first = data.first().unwrap_or(&0);
println!("第一个: {}", first);
// ✅ 使用 match 或 if let
if let Some(first) = data.first() {
println!("第一个: {}", first);
}
// ✅ 使用 ? 操作符传播错误
fn get_first(data: &[i32]) -> Option<i32> {
Some(*data.first()?)
}
println!("第一个: {:?}", get_first(&data));
}
25.5 项目结构最佳实践
目录布局
my-project/
├── Cargo.toml
├── Cargo.lock
├── README.md
├── LICENSE-MIT
├── LICENSE-APACHE
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── lib.rs # 库入口
│ ├── main.rs # 二进制入口
│ ├── config.rs # 配置
│ ├── error.rs # 错误类型
│ ├── models/ # 数据模型
│ │ ├── mod.rs
│ │ └── user.rs
│ └── services/ # 业务逻辑
│ ├── mod.rs
│ └── auth.rs
├── tests/ # 集成测试
│ └── integration_test.rs
├── benches/ # 基准测试
│ └── benchmark.rs
└── examples/ # 示例代码
└── basic.rs
错误处理策略
// src/error.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("数据库错误: {0}")]
Database(#[from] sqlx::Error),
#[error("配置错误: {0}")]
Config(String),
#[error("未找到: {0}")]
NotFound(String),
#[error("未授权")]
Unauthorized,
}
// 库使用 thiserror 定义具体错误类型
// 应用使用 anyhow 统一处理
25.6 文档最佳实践
文档注释
/// 计算两个数的和
///
/// # 参数
///
/// * `a` - 第一个加数
/// * `b` - 第二个加数
///
/// # 返回值
///
/// 返回两个数的和
///
/// # 示例
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// 如果结果溢出会 panic(debug 模式)
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
文档生成
# 生成并打开文档
cargo doc --open
# 包含私有项的文档
cargo doc --document-private-items
# 检查文档链接
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
25.7 测试最佳实践
#[cfg(test)]
mod tests {
use super::*;
// 命名:test_被测试的功能_场景
#[test]
fn test_add_positive_numbers() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative_numbers() {
assert_eq!(add(-1, -2), -3);
}
#[test]
#[should_panic(expected = "overflow")]
fn test_add_overflow_panics_in_debug() {
let _ = i32::MAX + 1;
}
// 测试 Result 返回
#[test]
fn test_parse_valid_input() -> Result<(), Box<dyn std::error::Error>> {
let value: i32 = "42".parse()?;
assert_eq!(value, 42);
Ok(())
}
// 集成测试放在 tests/ 目录
// 性能敏感的测试使用 benches/ 目录
}
25.8 CI/CD 检查清单
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Format
run: cargo fmt -- --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Test
run: cargo test
- name: Doc
run: cargo doc --no-deps
env:
RUSTDOCFLAGS: "-D warnings"
25.9 安全最佳实践
| 实践 | 说明 |
|---|
cargo audit | 定期检查依赖安全漏洞 |
cargo deny | 策略检查依赖许可证 |
| 最小化 unsafe | 尽量减少 unsafe 代码 |
| 安全依赖 | 选择维护活跃的 crate |
| 输入验证 | 始终验证外部输入 |
| 密钥管理 | 不要硬编码密钥,使用环境变量 |
25.10 本章小结
| 要点 | 说明 |
|---|
| rustfmt | 代码格式化,统一风格 |
| clippy | 静态分析,发现常见问题 |
| 性能优化 | 预分配、避免不必要的 clone、使用迭代器 |
| 常见陷阱 | 整数溢出、浮点比较、闭包捕获 |
| 文档注释 | 使用 /// 编写文档和示例 |
| CI/CD | 自动化 fmt、clippy、test、doc 检查 |
扩展阅读
- Rust API Guidelines — API 设计指南
- Rust Design Patterns — 设计模式
- Clippy Lints — 完整 lint 列表
- Rust Performance Book — 性能优化
- Command Line Applications in Rust — CLI 开发指南
恭喜你完成了全部 25 章 Rust 教程!继续实践和阅读官方文档,你会在 Rust 之旅上越走越远。🦀