Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust 最佳实践摘要

你将学到: 编写地道 Rust 的实用指南 — 代码组织、命名约定、错误处理模式与文档。你会经常回顾的快速参考章节。

代码组织

  • 优先小函数:易于测试与推理
  • 使用描述性名称calculate_total_price() 而非 calc()
  • 将相关功能分组:使用模块与独立文件
  • 编写文档:对公共 API 使用 ///

错误处理

  • 除非确定不会 panic,避免 unwrap():仅在 100% 确信时使用
#![allow(unused)]
fn main() {
// Bad: Can panic
let value = some_option.unwrap();

// Good: Handle the None case
let value = some_option.unwrap_or(default_value);
let value = some_option.unwrap_or_else(|| expensive_computation());
let value = some_option.unwrap_or_default(); // Uses Default trait

// For Result<T, E>
let value = some_result.unwrap_or(fallback_value);
let value = some_result.unwrap_or_else(|err| {
    eprintln!("Error occurred: {err}");
    default_value
});
}
  • 用描述性消息使用 expect():当 unwrap 合理时,说明原因
#![allow(unused)]
fn main() {
let config = std::env::var("CONFIG_PATH")
    .expect("CONFIG_PATH environment variable must be set");
}
  • 可失败操作返回 Result<T, E>:让调用方决定如何处理错误
  • 自定义错误类型使用 thiserror:比手写实现更顺手
#![allow(unused)]
fn main() {
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("Parse error: {message}")]
    Parse { message: String },
    
    #[error("Value {value} is out of range")]
    OutOfRange { value: i32 },
}
}
  • ? 运算符链式传播错误:沿调用栈向上传递
  • 优先 thiserror 而非 anyhow:我们团队约定用 #[derive(thiserror::Error)] 定义显式错误枚举,以便调用方匹配具体变体。anyhow::Error 适合快速原型,但会抹平错误类型,使调用方难以处理特定失败。库与生产代码用 thiserror;仅在只需打印错误的临时脚本或顶层二进制中用 anyhow
  • 何时 unwrap() 可接受
    • 单元测试assert_eq!(result.unwrap(), expected)
    • 原型:会被替换的临时代码
    • 不会失败的操作:能证明不会失败时
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3];
let first = numbers.get(0).unwrap(); // Safe: we just created the vec with elements

// Better: Use expect() with explanation
let first = numbers.get(0).expect("numbers vec is non-empty by construction");
}
  • 快速失败:尽早检查前置条件并立即返回错误

内存管理

  • 优先借用而非 clone:尽可能用 &T 代替 clone
  • 谨慎使用 Rc<T>:仅在需要共享所有权时
  • 限制生命周期:用作用域 {} 控制值何时 drop
  • 公共 API 避免 RefCell<T>:内部可变性保持内部

性能

  • 先 profile 再优化:使用 cargo bench 与 profiling 工具
  • 优先迭代器而非循环:更可读且往往更快
  • 需要所有权时用 &str 而非 String
  • 大栈对象考虑 Box<T>:必要时移到堆上

应实现的基本 Trait

每种类型都应考虑的核心 Trait

创建自定义类型时,考虑实现这些基础 Trait,使类型在 Rust 中感觉原生:

Debug 与 Display

#![allow(unused)]
fn main() {
use std::fmt;

#[derive(Debug)]  // Automatic implementation for debugging
struct Person {
    name: String,
    age: u32,
}

// Manual Display implementation for user-facing output
impl fmt::Display for Person {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} (age {})", self.name, self.age)
    }
}

// Usage:
let person = Person { name: "Alice".to_string(), age: 30 };
println!("{:?}", person);  // Debug: Person { name: "Alice", age: 30 }
println!("{}", person);    // Display: Alice (age 30)
}

Clone 与 Copy

#![allow(unused)]
fn main() {
// Copy: Implicit duplication for small, simple types
#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

// Clone: Explicit duplication for complex types
#[derive(Debug, Clone)]
struct Person {
    name: String,  // String doesn't implement Copy
    age: u32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = p1;  // Copy (implicit)

let person1 = Person { name: "Bob".to_string(), age: 25 };
let person2 = person1.clone();  // Clone (explicit)
}

PartialEq 与 Eq

#![allow(unused)]
fn main() {
#[derive(Debug, PartialEq, Eq)]
struct UserId(u64);

#[derive(Debug, PartialEq)]
struct Temperature {
    celsius: f64,  // f64 doesn't implement Eq (due to NaN)
}

let id1 = UserId(123);
let id2 = UserId(123);
assert_eq!(id1, id2);  // Works because of PartialEq

let temp1 = Temperature { celsius: 20.0 };
let temp2 = Temperature { celsius: 20.0 };
assert_eq!(temp1, temp2);  // Works with PartialEq
}

PartialOrd 与 Ord

#![allow(unused)]
fn main() {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Priority(u8);

let high = Priority(1);
let low = Priority(10);
assert!(high < low);  // Lower numbers = higher priority

// Use in collections
let mut priorities = vec![Priority(5), Priority(1), Priority(8)];
priorities.sort();  // Works because Priority implements Ord
}

Default

#![allow(unused)]
fn main() {
#[derive(Debug, Default)]
struct Config {
    debug: bool,           // false (default)
    max_connections: u32,  // 0 (default)
    timeout: Option<u64>,  // None (default)
}

// Custom Default implementation
impl Default for Config {
    fn default() -> Self {
        Config {
            debug: false,
            max_connections: 100,  // Custom default
            timeout: Some(30),     // Custom default
        }
    }
}

let config = Config::default();
let config = Config { debug: true, ..Default::default() };  // Partial override
}

From 与 Into

#![allow(unused)]
fn main() {
struct UserId(u64);
struct UserName(String);

// Implement From, and Into comes for free
impl From<u64> for UserId {
    fn from(id: u64) -> Self {
        UserId(id)
    }
}

impl From<String> for UserName {
    fn from(name: String) -> Self {
        UserName(name)
    }
}

impl From<&str> for UserName {
    fn from(name: &str) -> Self {
        UserName(name.to_string())
    }
}

// Usage:
let user_id: UserId = 123u64.into();         // Using Into
let user_id = UserId::from(123u64);          // Using From
let username = UserName::from("alice");      // &str -> UserName
let username: UserName = "bob".into();       // Using Into
}

TryFrom 与 TryInto

#![allow(unused)]
fn main() {
use std::convert::TryFrom;

struct PositiveNumber(u32);

#[derive(Debug)]
struct NegativeNumberError;

impl TryFrom<i32> for PositiveNumber {
    type Error = NegativeNumberError;
    
    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value >= 0 {
            Ok(PositiveNumber(value as u32))
        } else {
            Err(NegativeNumberError)
        }
    }
}

// Usage:
let positive = PositiveNumber::try_from(42)?;     // Ok(PositiveNumber(42))
let error = PositiveNumber::try_from(-5);         // Err(NegativeNumberError)
}

Serde(序列化)

#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

// Automatic JSON serialization/deserialization
let user = User {
    id: 1,
    name: "Alice".to_string(),
    email: "alice@example.com".to_string(),
};

let json = serde_json::to_string(&user)?;
let deserialized: User = serde_json::from_str(&json)?;
}

Trait 实现清单

对新类型,可参考此清单:

#![allow(unused)]
fn main() {
#[derive(
    Debug,          // [OK] Always implement for debugging
    Clone,          // [OK] If the type should be duplicatable
    PartialEq,      // [OK] If the type should be comparable
    Eq,             // [OK] If comparison is reflexive/transitive
    PartialOrd,     // [OK] If the type has ordering
    Ord,            // [OK] If ordering is total
    Hash,           // [OK] If type will be used as HashMap key
    Default,        // [OK] If there's a sensible default value
)]
struct MyType {
    // fields...
}

// Manual implementations to consider:
impl Display for MyType { /* user-facing representation */ }
impl From<OtherType> for MyType { /* convenient conversion */ }
impl TryFrom<FallibleType> for MyType { /* fallible conversion */ }
}

何时不应实现 Trait

  • 堆数据类型不要实现 CopyStringVecHashMap
  • 值可能为 NaN 时不要实现 Eq:含 f32/f64 的类型
  • 没有合理默认值时不要实现 Default:文件句柄、网络连接
  • clone 昂贵时不要实现 Clone:大型数据结构(考虑 Rc<T>

摘要:Trait 收益

Trait收益何时使用
Debugprintln!("{:?}", value)几乎总是(极少数例外)
Displayprintln!("{}", value)面向用户的类型
Clonevalue.clone()显式复制有意义时
Copy隐式复制小型简单类型
PartialEq==!=大多数类型
Eq自反相等数学上相等可靠时
PartialOrd<><=>=有自然序的类型
Ord.sort()BinaryHeap全序时
HashHashMap用作 map 键的类型
DefaultDefault::default()有明显默认值的类型
From/Into便捷转换常见类型转换
TryFrom/TryInto可失败转换可能失败的转换