rust-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRust Best Practices
Rust编码最佳实践
Based on Microsoft Pragmatic Rust Guidelines and Rust community standards.
基于Microsoft Pragmatic Rust指南及Rust社区标准。
Core Principles
核心原则
- Leverage the type system — Use types to make invalid states unrepresentable
- Embrace ownership — Work with the borrow checker, not against it
- Explicit over implicit — Be clear about fallibility, mutability, and lifetimes
- Zero-cost abstractions — Use iterators, generics, and traits without runtime cost
- Fail fast, recover gracefully — Validate early, handle errors explicitly
- 利用类型系统 — 使用类型确保无效状态无法被表示
- 拥抱所有权机制 — 配合借用检查器工作,而非与其对抗
- 显式优于隐式 — 明确标注可失败性、可变性和生命周期
- 零成本抽象 — 使用迭代器、泛型和 trait,且无运行时开销
- 快速失败,优雅恢复 — 尽早校验,显式处理错误
Error Handling
错误处理
Use thiserror
for Libraries
thiserror为库使用thiserror
thiserrorrust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("Not found: {0}")]
NotFound(String),
}rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("Not found: {0}")]
NotFound(String),
}Use anyhow
for Applications
anyhow为应用使用anyhow
anyhowrust
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = load_config()
.context("Failed to load configuration")?;
run_app(config)?;
Ok(())
}rust
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = load_config()
.context("Failed to load configuration")?;
run_app(config)?;
Ok(())
}Never Panic in Libraries
库中切勿使用Panic
rust
// ❌ BAD
pub fn get_item(index: usize) -> &Item {
&self.items[index] // Panics on out-of-bounds
}
// ✅ GOOD
pub fn get_item(&self, index: usize) -> Option<&Item> {
self.items.get(index)
}
// ✅ GOOD - when you need Result
pub fn get_item(&self, index: usize) -> Result<&Item, Error> {
self.items.get(index).ok_or(Error::NotFound(index))
}rust
// ❌ 不良实践
pub fn get_item(index: usize) -> &Item {
&self.items[index] // 越界时会Panic
}
// ✅ 最佳实践
pub fn get_item(&self, index: usize) -> Option<&Item> {
self.items.get(index)
}
// ✅ 最佳实践 - 需要返回Result时
pub fn get_item(&self, index: usize) -> Result<&Item, Error> {
self.items.get(index).ok_or(Error::NotFound(index))
}API Design
API设计
Use Builder Pattern for Complex Configs
复杂配置使用构建器模式
rust
pub struct Client {
url: String,
timeout: Duration,
retries: u32,
}
impl Client {
pub fn builder(url: impl Into<String>) -> ClientBuilder {
ClientBuilder {
url: url.into(),
timeout: Duration::from_secs(30),
retries: 3,
}
}
}
pub struct ClientBuilder {
url: String,
timeout: Duration,
retries: u32,
}
impl ClientBuilder {
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
pub fn build(self) -> Client {
Client {
url: self.url,
timeout: self.timeout,
retries: self.retries,
}
}
}rust
pub struct Client {
url: String,
timeout: Duration,
retries: u32,
}
impl Client {
pub fn builder(url: impl Into<String>) -> ClientBuilder {
ClientBuilder {
url: url.into(),
timeout: Duration::from_secs(30),
retries: 3,
}
}
}
pub struct ClientBuilder {
url: String,
timeout: Duration,
retries: u32,
}
impl ClientBuilder {
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
pub fn build(self) -> Client {
Client {
url: self.url,
timeout: self.timeout,
retries: self.retries,
}
}
}Use Newtype Pattern
使用Newtype模式
rust
// ❌ BAD - primitive obsession
fn create_user(name: String, email: String, age: u32) -> User { ... }
// ✅ GOOD - newtype wrappers
pub struct Username(String);
pub struct Email(String);
pub struct Age(u32);
impl Email {
pub fn new(email: impl Into<String>) -> Result<Self, ValidationError> {
let email = email.into();
if email.contains('@') {
Ok(Self(email))
} else {
Err(ValidationError::InvalidEmail)
}
}
}
fn create_user(name: Username, email: Email, age: Age) -> User { ... }rust
// ❌ 不良实践 - 原始类型滥用
fn create_user(name: String, email: String, age: u32) -> User { ... }
// ✅ 最佳实践 - Newtype包装器
pub struct Username(String);
pub struct Email(String);
pub struct Age(u32);
impl Email {
pub fn new(email: impl Into<String>) -> Result<Self, ValidationError> {
let email = email.into();
if email.contains('@') {
Ok(Self(email))
} else {
Err(ValidationError::InvalidEmail)
}
}
}
fn create_user(name: Username, email: Email, age: Age) -> User { ... }Accept impl Trait
for Flexibility
impl Trait使用impl Trait
提升灵活性
impl Traitrust
// ❌ BAD - overly specific
pub fn process(items: Vec<String>) { ... }
// ✅ GOOD - accept any iterable
pub fn process(items: impl IntoIterator<Item = impl AsRef<str>>) {
for item in items {
println!("{}", item.as_ref());
}
}rust
// ❌ 不良实践 - 过于具体
pub fn process(items: Vec<String>) { ... }
// ✅ 最佳实践 - 支持任何可迭代类型
pub fn process(items: impl IntoIterator<Item = impl AsRef<str>>) {
for item in items {
println!("{}", item.as_ref());
}
}Performance
性能优化
Avoid Unnecessary Clones
避免不必要的克隆
rust
// ❌ BAD
fn process(data: &String) {
let owned = data.clone(); // Unnecessary allocation
do_something(owned);
}
// ✅ GOOD
fn process(data: &str) {
do_something(data);
}rust
// ❌ 不良实践
fn process(data: &String) {
let owned = data.clone(); // 不必要的内存分配
do_something(owned);
}
// ✅ 最佳实践
fn process(data: &str) {
do_something(data);
}Use Cow
for Conditional Ownership
Cow使用Cow
实现条件式所有权
Cowrust
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}rust
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}Prefer Iterators Over Loops
优先使用迭代器而非循环
rust
// ❌ BAD
let mut result = Vec::new();
for item in items {
if item.is_valid() {
result.push(item.transform());
}
}
// ✅ GOOD
let result: Vec<_> = items
.into_iter()
.filter(|item| item.is_valid())
.map(|item| item.transform())
.collect();rust
// ❌ 不良实践
let mut result = Vec::new();
for item in items {
if item.is_valid() {
result.push(item.transform());
}
}
// ✅ 最佳实践
let result: Vec<_> = items
.into_iter()
.filter(|item| item.is_valid())
.map(|item| item.transform())
.collect();Async Patterns
异步模式
Use tokio
Runtime
tokio使用tokio
运行时
tokiorust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::init();
run().await
}rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::init();
run().await
}Structured Concurrency with JoinSet
JoinSet使用JoinSet
实现结构化并发
JoinSetrust
use tokio::task::JoinSet;
async fn process_all(urls: Vec<String>) -> Vec<Result<Response, Error>> {
let mut set = JoinSet::new();
for url in urls {
set.spawn(async move {
fetch(&url).await
});
}
let mut results = Vec::new();
while let Some(res) = set.join_next().await {
results.push(res.unwrap());
}
results
}rust
use tokio::task::JoinSet;
async fn process_all(urls: Vec<String>) -> Vec<Result<Response, Error>> {
let mut set = JoinSet::new();
for url in urls {
set.spawn(async move {
fetch(&url).await
});
}
let mut results = Vec::new();
while let Some(res) = set.join_next().await {
results.push(res.unwrap());
}
results
}Use #[instrument]
for Tracing
#[instrument]使用#[instrument]
进行追踪
#[instrument]rust
use tracing::instrument;
#[instrument(skip(password))]
async fn login(username: &str, password: &str) -> Result<Token> {
tracing::info!("Attempting login");
// ...
}rust
use tracing::instrument;
#[instrument(skip(password))]
async fn login(username: &str, password: &str) -> Result<Token> {
tracing::info!("Attempting login");
// ...
}Testing
测试
Use #[test]
and proptest
#[test]proptest使用#[test]
和proptest
#[test]proptestrust
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_basic() {
assert_eq!(add(2, 2), 4);
}
proptest! {
#[test]
fn test_add_commutative(a: i32, b: i32) {
prop_assert_eq!(add(a, b), add(b, a));
}
}
}rust
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_basic() {
assert_eq!(add(2, 2), 4);
}
proptest! {
#[test]
fn test_add_commutative(a: i32, b: i32) {
prop_assert_eq!(add(a, b), add(b, a));
}
}
}Use mockall
for Mocking
mockall使用mockall
进行模拟测试
mockallrust
#[cfg_attr(test, mockall::automock)]
trait Database {
async fn get(&self, id: u64) -> Result<Record>;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_with_mock() {
let mut mock = MockDatabase::new();
mock.expect_get()
.returning(|_| Ok(Record::default()));
let service = Service::new(mock);
assert!(service.process(1).await.is_ok());
}
}rust
#[cfg_attr(test, mockall::automock)]
trait Database {
async fn get(&self, id: u64) -> Result<Record>;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_with_mock() {
let mut mock = MockDatabase::new();
mock.expect_get()
.returning(|_| Ok(Record::default()));
let service = Service::new(mock);
assert!(service.process(1).await.is_ok());
}
}Common Anti-Patterns to Avoid
需避免的常见反模式
| Anti-Pattern | Better Alternative |
|---|---|
| |
| Restructure code, use references |
| Concrete error types with |
| |
Manual | RAII with struct destructors |
| Safe abstractions first |
| Message passing, channels |
| Blocking in async context | |
| 反模式 | 更佳替代方案 |
|---|---|
到处使用 | 配合合适错误类型的 |
用 | 重构代码,使用引用 |
| 用 |
所有文本都用 | |
手动实现 | 用结构体析构函数实现RAII |
无正当理由使用 | 优先使用安全抽象 |
过度使用 | 消息传递、通道 |
| 异步上下文里阻塞操作 | CPU密集型工作使用 |