rust-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rust Best Practices

Rust编码最佳实践

Based on Microsoft Pragmatic Rust Guidelines and Rust community standards.
基于Microsoft Pragmatic Rust指南及Rust社区标准。

Core Principles

核心原则

  1. Leverage the type system — Use types to make invalid states unrepresentable
  2. Embrace ownership — Work with the borrow checker, not against it
  3. Explicit over implicit — Be clear about fallibility, mutability, and lifetimes
  4. Zero-cost abstractions — Use iterators, generics, and traits without runtime cost
  5. Fail fast, recover gracefully — Validate early, handle errors explicitly

  1. 利用类型系统 — 使用类型确保无效状态无法被表示
  2. 拥抱所有权机制 — 配合借用检查器工作,而非与其对抗
  3. 显式优于隐式 — 明确标注可失败性、可变性和生命周期
  4. 零成本抽象 — 使用迭代器、泛型和 trait,且无运行时开销
  5. 快速失败,优雅恢复 — 尽早校验,显式处理错误

Error Handling

错误处理

Use
thiserror
for Libraries

为库使用
thiserror

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),
}
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

rust
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
提升灵活性

rust
// ❌ 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
实现条件式所有权

rust
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
运行时

rust
#[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
实现结构化并发

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
}
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]
进行追踪

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

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));
        }
    }
}
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
进行模拟测试

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());
    }
}

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-PatternBetter Alternative
unwrap()
everywhere
?
operator with proper error types
clone()
to satisfy borrow checker
Restructure code, use references
Box<dyn Error>
Concrete error types with
thiserror
String
for all text
&str
,
Cow<str>
, or domain types
Manual
Drop
for cleanup
RAII with struct destructors
unsafe
without justification
Safe abstractions first
Arc<Mutex<_>>
overuse
Message passing, channels
Blocking in async context
spawn_blocking
for CPU work

反模式更佳替代方案
到处使用
unwrap()
配合合适错误类型的
?
运算符
clone()
满足借用检查器
重构代码,使用引用
Box<dyn Error>
thiserror
定义具体错误类型
所有文本都用
String
&str
Cow<str>
或领域类型
手动实现
Drop
进行清理
用结构体析构函数实现RAII
无正当理由使用
unsafe
优先使用安全抽象
过度使用
Arc<Mutex<_>>
消息传递、通道
异步上下文里阻塞操作CPU密集型工作使用
spawn_blocking

References

参考资料