Rust设计模式指南Skill rust-design-patterns

Rust设计模式指南提供Rust编程语言的惯用法和最佳实践,涵盖所有权管理、借用检查器解决方案、API设计模式、性能优化技巧和常见反模式。适用于Rust开发者解决生命周期错误、内存安全问题和编写高效地道的Rust代码。关键词:Rust编程,设计模式,所有权模型,借用检查器,内存安全,API设计,性能优化,反模式

后端开发 0 次安装 0 次浏览 更新于 2/28/2026

名称: rust-设计模式 描述: “用于编写地道Rust代码的惯用法和设计模式。使用场景:(1)编写新的Rust代码,(2)审查Rust代码,(3)解决借用检查器问题,(4)设计Rust API。触发条件:Rust所有权问题、生命周期错误、API设计问题、‘如何在Rust中地道地实现X’。” 用户可调用: false

Rust 设计模式

编写地道Rust代码的惯用法和模式。重点关注利用所有权、借用和类型系统的Rust特有模式。

决策树

遇到问题?
├── 借用检查器错误?
│   ├── 需要从&mut枚举中移出值 → 使用mem::take/replace
│   ├── 需要独立的字段借用 → 结构体解构
│   ├── 想用克隆? → 检查:Rc/Arc?或重构所有权?
│   └── 生命周期太短 → 考虑使用拥有所有权的类型或'static
│
├── API设计问题?
│   ├── 多个构造参数 → 建造者模式
│   ├── 接受灵活输入 → 借用类型(&str, &[T])
│   ├── 编译时类型安全 → 新类型模式
│   ├── 需要默认值 → Default trait + 结构体更新语法
│   └── 需要资源清理 → RAII守卫(Drop trait)
│
├── FFI边界问题?
│   ├── 错误处理 → 整数错误码 + 错误描述函数
│   ├── 字符串传递 → CString/CStr模式
│   └── 对象生命周期 → 不透明指针配合显式释放
│
├── 不安全代码?
│   ├── 需要不安全操作 → 封装在小模块中并提供安全包装
│   └── FFI类型 → 类型整合到不透明包装器中
│
└── 性能问题?
    ├── 避免单态化膨胀 → 栈上动态分发
    └── 减少内存分配 → 使用mem::take替代clone

快速模式

借用类型(关键)

优先使用&str而非&String&[T]而非&Vec<T>

// 不好:只接受&String
fn process(s: &String) { }

// 好:接受&String, &str, 字符串字面量
fn process(s: &str) { }

// 用法:所有类型都适用于&str
process(&my_string);      // String
process("literal");       // &'static str
process(&my_string[1..5]); // 切片

原因:Deref强制转换允许&String&str,但反过来不行。使用借用类型可以接受更多输入类型。

mem::take模式(关键)

&mut中移出拥有所有权的值而无需克隆:

use std::mem;

enum State {
    Active { data: String },
    Inactive,
}

fn deactivate(state: &mut State) {
    if let State::Active { data } = state {
        // 无需克隆即可获得所有权
        let owned_data = mem::take(data);
        *state = State::Inactive;
        // 使用owned_data...
    }
}

适用场景:更改枚举变体同时保留内部拥有所有权的数据。避免克隆反模式。

新类型模式

零运行时成本的类型安全:

// 不同的类型防止混淆
struct UserId(u64);
struct OrderId(u64);

fn get_order(user: UserId, order: OrderId) { }

// 编译错误:不能混淆ID
// get_order(order_id, user_id);

适用场景:需要在相同底层类型之间进行编译时区分,或需要自定义trait实现。

建造者模式

用于复杂构造:

#[derive(Default)]
struct RequestBuilder {
    url: String,
    timeout: Option<u32>,
    headers: Vec<(String, String)>,
}

impl RequestBuilder {
    fn url(mut self, url: impl Into<String>) -> Self {
        self.url = url.into();
        self
    }

    fn timeout(mut self, ms: u32) -> Self {
        self.timeout = Some(ms);
        self
    }

    fn build(self) -> Request {
        Request { /* ... */ }
    }
}

// 用法
let req = RequestBuilder::default()
    .url("https://example.com")
    .timeout(5000)
    .build();

适用场景:多个可选参数,或构造过程需要验证/副作用。

RAII守卫

通过所有权进行资源管理:

struct FileGuard {
    file: File,
}

impl Drop for FileGuard {
    fn drop(&mut self) {
        // 守卫超出作用域时自动运行清理
        self.file.sync_all().ok();
    }
}

fn process() -> Result<()> {
    let guard = FileGuard { file: File::open("data.txt")? };
    // 即使提前返回或发生panic,Drop也会运行
    do_work()?;
    Ok(())
} // guard.drop()在此处调用

适用场景:需要保证清理(锁、文件、连接、事务)。

Default + 结构体更新

使用默认值进行部分初始化:

#[derive(Default)]
struct Config {
    host: String,
    port: u16,
    timeout: u32,
    retries: u8,
}

let config = Config {
    host: "localhost".into(),
    port: 8080,
    ..Default::default()  // timeout=0, retries=0
};

栈上动态分发

避免为trait对象进行堆分配:

use std::io::{self, Read};

fn process(use_stdin: bool) -> io::Result<String> {
    let readable: &mut dyn Read = if use_stdin {
        &mut io::stdin()
    } else {
        &mut std::fs::File::open("input.txt")?
    };

    let mut buf = String::new();
    readable.read_to_string(&mut buf)?;
    Ok(buf)
}

适用场景:需要动态分发但不想使用Box分配。自Rust 1.79起,生命周期扩展使此用法更符合人体工程学。

Option作为迭代器

Option实现了IntoIterator(0或1个元素):

let maybe_name = Some("Turing");
let mut names = vec!["Curry", "Kleene"];

// 使用Option扩展
names.extend(maybe_name);

// 与Option链式调用
for name in names.iter().chain(maybe_name.iter()) {
    println!("{name}");
}

提示:对于总是Some的情况,优先使用std::iter::once(value)

闭包捕获控制

通过重新绑定控制闭包捕获的内容:

use std::rc::Rc;

let num1 = Rc::new(1);
let num2 = Rc::new(2);

let closure = {
    let num2 = num2.clone();  // 移动前克隆
    let num1 = num1.as_ref(); // 借用
    move || { *num1 + *num2 }
};
// num1仍可用,num2已被克隆

临时可变性

设置完成后使变量不可变:

// 方法1:嵌套块
let data = {
    let mut data = get_vec();
    data.sort();
    data
};
// 此处data不可变

// 方法2:重新绑定
let mut data = get_vec();
data.sort();
let data = data;  // 现在不可变

错误时返回消耗的参数

如果函数消耗了参数,在错误时返回它以进行重试:

pub struct SendError(pub String);  // 包含原始值

pub fn send(value: String) -> Result<(), SendError> {
    if can_send() {
        do_send(&value);
        Ok(())
    } else {
        Err(SendError(value))  // 调用者可以重试
    }
}

// 用法:无需克隆的重试循环
let mut msg = "hello".to_string();
loop {
    match send(msg) {
        Ok(()) => break,
        Err(SendError(m)) => { msg = m; }  // 恢复并重试
    }
}

示例String::from_utf8返回包含原始Vec<u8>FromUtf8Error

反模式检查清单

检查代码中这些常见错误:

  • [ ] 为满足借用检查器而克隆 - 通常表示所有权设计问题。考虑使用mem::takeRc/Arc或重构。

  • [ ] 在库中使用#![deny(warnings)] - 在新Rust版本中会破坏下游代码。在CI中使用RUSTFLAGS="-D warnings"代替。

  • [ ] 使用Deref实现继承 - 行为令人意外,不提供真正的子类型化。使用组合+委托或trait。

  • [ ] 函数参数使用&String&Vec<T> - 使用&str&[T]以获得灵活性。

  • [ ] 手动调用drop() - 通常不必要。如果需要控制顺序,优先使用作用域块。

  • [ ] 忽略clippy对.clone()的建议 - 运行cargo clippy查找不必要的克隆。

参考资料

包含完整示例的详细模式:

  • ownership-patterns.md - 借用检查器模式:mem::take/replace、结构体解构、RAII守卫、Rc/Arc决策
  • api-design.md - API模式:借用类型、建造者、新类型、Default trait、FFI
  • common-pitfalls.md - 详细反模式:克隆滥用、deny(warnings)、Deref多态