设计权衡

本节是关于 Rust 中的 设计权衡。要成为一名高效的 Rust 工程师,仅仅了解 Rust 的工作原理是不够的。您必须决定 Rust 的众多工具中哪些适合给定的工作。在本节中,我们将为您提供一系列关于您对 Rust 中设计权衡的理解的测验。每次测验后,我们将深入解释我们每个问题的理由。

这是一个问题示例。它将首先描述一个软件案例研究,其中包含一系列设计方案

背景: 您正在设计一个具有全局配置的应用程序,例如包含命令行标志。

功能: 应用程序需要在整个应用程序中传递对该配置的不可变引用。

设计方案: 以下是实现该功能的几种建议设计方案。

use std::rc::Rc;
use std::sync::Arc;

struct Config { 
    flags: Flags,
    // .. more fields ..
}

// Option 1: use a reference
struct ConfigRef<'a>(&'a Config);

// Option 2: use a reference-counted pointer
struct ConfigRef(Rc<Config>);

// Option 3: use an atomic reference-counted pointer
struct ConfigRef(Arc<Config>);

仅给定背景和关键功能,所有三种设计方案都是潜在的候选方案。我们需要更多关于系统目标的信息来决定哪种方案最有意义。因此,我们给出一个新的要求

选择满足以下要求的每个设计选项

要求: 配置引用必须在多个线程之间共享。

答案

选项 1
选项 2
选项 3

用正式术语来说,这意味着 ConfigRef 实现了 SendSync。假设 Config: Send + Sync,那么 &ConfigArc<Config> 都满足此要求,但 Rc 不满足(因为非原子引用计数指针不是线程安全的)。因此,选项 2 不满足要求,而选项 3 满足。

我们可能还会倾向于得出结论,选项 1 不满足要求,因为像 thread::spawn 这样的函数要求移动到线程中的所有数据只能包含具有 'static 生命周期 的引用。但是,这并不能排除选项 1,原因有二

  1. Config 可以存储为全局静态变量(例如,使用 OnceLock),因此可以构造 &'static Config 引用。
  2. 并非所有并发机制都要求 'static 生命周期,例如 thread::scope

因此,如上所述的要求仅排除非 Send 类型,我们认为选项 1 和 3 是正确的答案。


现在您尝试下面的问题!每个部分都包含一个专注于单个场景的测验。完成测验,并确保阅读每次测验后的答案上下文。这些问题既是实验性的又是主观的——如果您不同意我们的答案,请通过 bug 按钮 🐞 向我们留下反馈。

除了每个测验之外,我们还提供了指向流行的 Rust crate 的链接,这些 crate 为测验提供了灵感。

参考

灵感来源: Bevy assets, Petgraph node indices, Cargo units

Trait 树

灵感来源: Yew components, Druid widgets

分发

灵感来源: Bevy systems, Diesel queries, Axum handlers

中间体

灵感来源: Serdeminiserde