解构 Rust 话语
Last updated on
前言
本书同时分析 Rust 编程语言的技术特性,以及围绕它形成的特定技术与社会话语。这里所说的解构,并不是预先否定某项技术或某种主张,而是把技术事实、语言所提供的保证、工程成本、对因果关系的解释、价值判断和修辞表达彼此分离,再分别检视其依据与适用范围。本书从历史和技术脉络出发,考察 Rust 为实现安全性、性能与并发性而选择的设计,以及这些选择伴随的成本与约束。
本书围绕以下问题展开讨论。
- Safe Rust 能防止哪些种类的内存错误和数据竞争?在面对
unsafe、外部函数接口(FFI)、逻辑错误与运行故障时,这些保证的有效范围到哪里为止? - 与 C++、基于垃圾收集器(GC)1 的语言、Ada 和 SPARK 的方法相比,Rust 的所有权、借用、生命周期、类型系统和零成本抽象具有什么优势与权衡?
- 企业和开源项目的采用案例及量化成果,在多大范围内能够证明 Rust 本身的效果?又该如何把这种效果与架构、算法、运行时、硬件和组织变化的效果区分开来?
- 对现有系统进行重构、渐进式现代化、选择性替换高风险组件以及全面重写,应在什么条件下加以区分和组合?
- 当有条件成立的技术优势被转换为适用于所有系统的普遍优越性,或被转换为对开发者智力与资格的判断时,会发生哪些逻辑跳跃?
- 声称某种语言是“唯一替代方案”时,预设了哪些需求和候选集合?如果省略这些前提与比较过程,会产生哪些逻辑错误?
为回答这些问题,本书优先采用语言规范与标准、官方项目文档、政府报告、CVE 记录、企业原始技术报告和同行评审研究等一手资料。审查量化主张时,会确认研究对象与样本、分子与分母、基线、工作负载、软硬件环境以及比较期间;并区分观测结果、统计估计、因果归因和作者解释,尽可能对成功与失败案例采用相同的证据标准。当资料只涉及特定组织或系统时,本书不会超出其范围作一般化推论,并同时说明不确定性与替代解释。
比较对象包括 C++、Java、C#、Go、Ada 和 SPARK。比较的目的不是把某种语言排列为另一种语言的简单替代品,而是考察不同语言与生态系统如何在性能、内存控制、开发生产力、可验证性、实时性、工具以及长期可维护性之间选择成本。Ada 和 SPARK 展示了另一种不依赖 GC 而追求安全性与可靠性的历史路径,也构成重要的比较对象。本书还把语言特性与变更策略分开,不把保留或废弃现有系统视为二选一,而是将重构、现代化、局部替换和重写评价为彼此独立的工程手段。
本书所说的Rust 话语,并不代表 Rust 基金会、核心开发团队或整个社区的官方立场。Rust 项目的官方渠道一直公开讨论并改善编译时间、异步编程、工具和安全边界等多方面课题。本书分析的是在部分公开技术论坛和社交媒体上反复观察到的特定论证方式。这些网络案例不是用于证明此类主张在整个社区中出现频率的统计样本,而是作为分析主张结构与话语功能的定性材料。
Rust 在不依赖 GC 的情况下结合了强有力的内存安全保证与高度控制能力,并在产业和开源项目中取得了重要成果。本书并非为了缩小这些成果,也不是为了推荐某种特定技术选择。其目的在于用同一标准检视优点与局限、保证与成本、观测与解释。因此,本书的结论也不是最终宣言,而是依据目前可确认的证据作出的判断;当出现更好的资料或反例时,结论可以修正。
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
目录
- 前言
- 第一部分:Rust 的出现与技术特性
- 第二部分:主要设计原则的技术分析
- 第三部分:生态系统现实与结构性成本
- 第四部分:技术共同体话语分析
- 第五部分:综合分析与结论
- 后记
- 附录:技术讨论中观察到的论证谬误案例分析
第一部分:Rust 的出现与技术特性
第一部分分析 Rust 编程语言如何应对系统编程领域的课题,以及人们通过哪些特性讨论它。
第一章考察 Rust 诞生背景中的性能与安全权衡,并介绍为此采用的主要技术特性,包括所有权模型、零成本抽象(ZCA)理念,以及以 Cargo 为代表的生态系统。
第二章分析这些技术基础如何与开发者体验(DX)、叙事和机构支持相互作用,并通过复杂因素影响采用。
1. Rust 语言简介及主要特性
本章并不只是罗列 Rust 的特性,而是检视用于分析内存安全保证边界以及设计优点与权衡的两个问题。第一,Rust 试图在系统编程中解决哪些缺陷与成本?第二,所有权、借用、生命周期、类型系统与零成本抽象所提供的保证延伸到哪里,又伴随哪些权衡?
为此,本章区分语言的设计目标、规范层面的保证、编译器与库的实现,以及在实际工作负载中观察到的结果。内容将依次考察所有权与借用、零成本抽象、类型与模式匹配、Cargo 生态系统,但不会把某项设计目标扩大为关于所有程序性能或安全性的普遍结论。工业采用成果与变更策略将在后续章节中按照独立的证据标准处理。
1.1 诞生背景:性能与安全的权衡
系统编程可能同时要求硬件控制、可预测的资源管理、吞吐量与延迟、内存安全,以及并发错误防止。不过,如果简单地把这些要求表述为“性能还是安全”的二选一,就会过度缩减历史上存在的选择。C 与 C++ 围绕低层控制和手动资源管理发展;Ada 与 SPARK 将强类型、运行时检查、契约与形式化验证结合起来;基于 GC 的语言则使用自动内存回收和托管运行时。哪种方法合适,取决于缺陷模型、实时性要求、工作负载、运行环境与验证要求。
Rust 起源于 Mozilla Research,并发展为一种同时追求内存安全、并发性、低层控制与性能的系统编程语言。2 “无需 GC 即可提供内存安全,并以能够与 C++ 竞争的性能为目标”是一项设计目标与价值主张,而不是保证每个 Rust 程序都与每个 C++ 程序具有相同性能,也不是保证运行时成本与失败会消失。因此,下面三个目标也必须在区分设计目标、语言保证、实现特性与观察结果的前提下理解。
安全性
Safe Rust 通过所有权、借用与类型规则防止某些无效内存访问和数据竞争。这项保证以编译器与库、使用 unsafe 实现的抽象以及 FFI 边界分别遵守其契约为前提。3 它不会自动保证不存在 panic、死锁、资源泄漏或耗尽、逻辑错误、所有安全漏洞,或服务连续性中断。第 1.2 节与第 3.2 节将具体检视准确的保证边界。
性能
Rust 无需强制垃圾收集器即可生成本机代码,并采用零成本抽象原则:高级抽象的设计不应要求可避免的额外运行时开销。4 这一原则并不表示每种抽象始终具有相同速度,也不表示编译时间、二进制大小、内存使用、调试难度与开发者认知成本为零。实际性能必须在明确说明工作负载、算法、优化、分配、I/O、库与硬件条件后进行测量。
并发性
Safe Rust 的所有权与类型系统可以防止数据竞争,但不会消除一般竞态条件、死锁、饥饿、优先级反转或分布式系统的一致性问题。5 因此,“无畏并发”应当被理解为范围明确的表述:编译阶段会阻止某些内存安全违规和数据竞争,而不是所有并发缺陷都不存在。
本章的重要出发点,是区分 Rust 在一种设计中同时追求安全性、性能和并发性这一事实,与这些目标并不等于所有环境中的观察结果。接下来的各节将依次检视所有权与借用提供的具体保证、被拒绝的有效程序、运行时检查与实现依赖,以及与其他设计之间的权衡。
1.2 通过所有权、借用和生命周期管理内存
本节不把所有权当作一句口号,而是将其分为三个层次加以考察。所有权表示清理值与资源的责任,借用在不转移所有权的情况下限制访问权限,生命周期则描述引用不得比其所指向的值使用得更久这一关系。这三个层次彼此关联,但并不是同一个概念。6
1. 所有权:清理值与资源的责任
在 Rust 中,每个值都有所有者,所有权可以通过赋值或函数调用发生移动。值被移动之后,编译器会拒绝通过原变量再次使用它。不过,整数等实现了 Copy 的值会在赋值时被复制,因此原变量仍可继续使用。所以,“所有赋值都是移动”并不是准确的说明。
所有者离开有效作用域时,值会按照 Drop 进行清理。拥有堆分配的类型可以在此时释放该分配,文件、套接字和锁等资源也可以与类型的析构过程绑定。这一规则是在 Safe Rust 中防止二次释放和释放后使用等错误的基础,但并不表示析构函数在所有终止路径上都必然执行。进程被强制终止或 abort、故意泄漏以及引用计数环等情况下,资源可能不会立即回收。
2. 借用:区别于所有权的访问权限
引用并不拥有其目标。共享引用 &T 在有效期间提供读取访问,可变引用 &mut T 则表示该期间内的排他访问。通常可以概括为“多个共享引用或一个可变引用”,但关键在于限制引用实际使用期间别名与修改同时发生。6
这些规则会在 Safe Rust 中阻止未经同步的并发读写所产生的数据竞争。不过,它们不会消除结果取决于执行顺序的一般竞态条件、死锁、饥饿或优先级反转。基于 UnsafeCell 的内部可变性、原始指针和 unsafe 代码还需要安全的外部接口维护额外的不变量。
3. 生命周期:引用有效性的关系
生命周期不是直接控制值何时销毁的运行时机制,而是描述引用之间必须满足何种有效期关系的静态契约。生命周期标注不会让引用存活得更久;它只是描述函数所接收与返回的引用之间的关系等信息,使编译器能够进行检查。6
当前借用检查器使用非词法生命周期(NLL),考虑引用的最后使用位置,而不只是所在代码块的末尾。尽管如此,静态分析无法完整判定所有会终止程序的语义,因此必然具有保守性,并会拒绝一些实际执行时安全的程序。Rust 项目将 Polonius alpha 的稳定化列为 2026 年目标,其中一个原因就是要接受更多当前分析无法表达的有效模式,包括条件借用和 lending iterator。7
4. Safe Rust 保证的前提与边界
Safe Rust 的核心保证,是安全代码本身不能引发未定义行为这一健全性(soundness)性质。不过,这项保证以编译器、标准库与第三方库、分配器、使用 unsafe 实现的抽象、操作系统接口及 FFI 代码分别遵守各自契约为前提。unsafe 并不是允许未定义行为的标记,而是表示实现者必须自行验证编译器无法检查的义务。3
因此,所有权与借用能够阻止悬垂引用、无效别名、二次释放和数据竞争等重要错误类别,但不会消除内存不足、资源耗尽、panic、abort、逻辑错误、死锁、外部代码违反契约或编译器缺陷。若 FFI 边界的 C 代码引发未定义行为,其影响可能扩展到包括 Rust 部分在内的整个程序。
5. 基本规则之外的所有权模式及其成本
现实中的数据结构有时很难仅凭单一所有权与静态借用来表达。Rust 因此提供一些类型,在保持安全抽象的同时改变检查时机或同步方式。8
Rc<T>在单线程中表示多个所有者,但需要堆分配以及引用计数的增减;强引用形成环时可能造成内存泄漏。RefCell<T>提供内部可变性,并在运行时而不是编译时检查借用规则。违反规则时会发生panic而不是未定义行为,但会增加状态跟踪与分支成本。Arc<T>使用原子引用计数实现跨线程共享。由于原子操作本身有成本,在不需要线程安全时可能不如Rc<T>。Mutex<T>只向已获取锁的代码授予内部值的可变访问。类型与 RAII 会结构化锁的释放,但获取锁、锁竞争的成本以及死锁可能性仍然存在。
这些类型并没有移除所有权规则。它们把静态检查难以表达的模式封装在安全 API 后面,同时选择运行时检查、堆分配、引用计数、原子操作与锁作为成本,并引入新的失败条件。
阶段性结论
从内存安全保证的边界来看,所有权、借用与生命周期在健全的 Safe Rust 边界内,会有力阻止特定的悬垂引用、二次释放、释放后使用、无效别名和数据竞争。不过,这项保证依赖 unsafe 实现与外部边界的契约,并不涵盖所有错误或运行故障。
从设计优点与成本来看,这种设计无需运行时垃圾收集器便可提供强静态保证,但要求通过类型和接口显式表达所有权关系,也可能拒绝部分有效程序。选择共享所有权或内部可变性时,静态成本并不会消失,而是转移为引用计数、运行时检查、原子操作和锁的成本。因此,与其把 Rust 的内存管理模型理解为“没有成本的安全”,不如把它评价为一种静态阻止特定错误、并在必要时通过类型显式选择替代成本的设计。
1.3 零成本抽象的谱系
零成本抽象并不是声称程序中的所有成本都为零,而是一项用于设计语言和库的原则。它要求未使用的功能不应产生时间或空间成本,并使实际使用的抽象能够与合理手写的低层实现竞争。这一原则在 C++ 的 zero-overhead principle 中得到系统表达,Rust 则把它作为同时追求内存安全与低层控制的一条设计轴线。9
C 的 struct、宏、inline、sizeof 等参与编译阶段或提供低层控制的功能影响了这段历史。不过,如果把所有编译时技术都称为零成本抽象的早期形式,概念范围就会过于宽泛。本节区分低层实现技术、语言层面的抽象原则以及特定编译器生成的观测结果。
1. 静态分派与单态化
实际使用的泛型函数和静态分派的 trait 会针对具体类型进行单态化(monomorphization)。这种方式在编译时确定调用目标,为直接调用、内联等优化创造机会。由于优化器可以同时看到具体类型与操作,抽象边界有时会从最终机器码中消失。9
不过,单态化并不保证“始终生成相同机器码”,也不保证“始终比手写代码更快”。生成结果可能随 rustc 与 LLVM 版本、优化级别、crate 边界、LTO、代码生成单元、目标 CPU 与启用的功能以及周边代码而变化。同一份源代码在开发构建和发布构建中可能得到截然不同的优化结果。10
2. 动态分派、分配与运行时检查
Rust 并非所有抽象都会在静态阶段被消除。dyn Trait 使用数据指针与虚方法表(vtable),在运行时选择调用目标。这会产生间接调用成本,并通常减少内联机会;但由于无需为每个具体类型复制一份代码,也可能减小代码体积。11
动态分派与堆分配也不是同一个概念。&dyn Trait 可以借用已有值,本身不要求堆分配;Box<dyn Trait> 则选择了基于 Box 的堆所有权表示。Vec 与 String 的分配和重新分配,以及 Box 的堆所有权,是所选数据结构的行为,不会因为被称为“抽象”就消失。数组与切片的安全索引在越界时还具有触发 panic 的语义;边界检查能否被消除是优化结果,而不是语言的普遍保证。11
3. 减少运行时开销时可能产生的成本
单态化会为具体类型生成机器码,因此可能有利于执行性能和内联,但也可能增加编译时间与二进制体积。代码体积增大还可能提高指令缓存压力,不过实际影响取决于调用频率、代码布局、目标处理器和工作负载。相反,动态分派会保留间接调用,却能减少代码重复。10
构建设置同样存在权衡。较高的优化级别与 LTO 能提供更多优化机会,但可能增加编译和链接时间;优化后的代码还可能因源代码顺序与执行状态被重排而更难调试。增加代码生成单元可以加快并行编译,却可能降低生成代码的性能。可维护性也必须同时评估抽象减少的代码重复,以及泛型 API、错误诊断和构建追踪带来的复杂度。因此,编译时间、二进制体积、指令缓存、调试与维护成本,是不同于运行时吞吐量的独立评价维度。10
4. 迭代器示例的保证范围
// 计算 1 到 99 中所有 3 的倍数的平方之和
let sum = (1..100).filter(|&x| x % 3 == 0).map(|x| x * x).sum::<u32>();
这段代码组合 filter、map 与 sum,以声明式方式表达计算。在优化构建中,适配器调用可能被内联,中间状态可能被消除,从而生成近似单个循环的代码。然而,仅凭这一示例不能断定所有迭代器链都与手写循环具有相同性能或机器码。Rust 官方书籍中的比较也只是观察到循环与迭代器在一个搜索工作负载中结果相近,并不是跨越不同输入和条件的全面等价证明。12
5. 比较性能主张所需的条件
有关零成本抽象的性能比较,至少应明确以下条件。
rustc、Cargo 与主要 crate 的版本- 目标三元组、CPU、启用的指令集功能与操作系统
- 开发、发布或自定义 profile,以及优化级别、LTO、代码生成单元和
panic策略 - 输入数据、工作负载、迭代次数、预热过程与测量方法
- 作为比较基线的循环或其他实现所使用的算法、分配与 I/O 条件
- 除延迟与吞吐量外,还包括编译时间、二进制体积与内存使用量
这些条件不同时,同一种语法抽象也可能产生不同结果。因此,不能把一个基准测试中的优势推广为整门语言或所有抽象的固定属性。
阶段性结论
从设计优点与成本来看,静态分派与单态化是把高级接口降低为可以直接调用和优化的具体代码的有力机制。不过,零成本抽象原则并不是一项规范级保证,不能保证每种抽象都产生相同机器码或性能。动态分派、堆分配、边界检查以及库的运行时行为,会随着所选表示和数据结构继续存在。
与其说 Rust 消除了成本,不如说它提供了一种选择在何处支付何种成本的设计。避免运行时间接调用,可能需要承担单态化带来的编译时间与代码体积成本;减少代码重复,则可能选择动态分派成本。因此,对零成本抽象的评价不应停留在口号上,而应同时比较具体工作负载、构建条件、生成代码与整个生命周期成本。
1.4 通过类型系统与模式匹配确保安全
Rust 的静态类型系统限制值的表示方式以及允许对其执行的操作。程序可以把数据可能处于的状态记录到类型中,编译器则能在编译时拒绝与该类型不一致的操作。不过,类型检查器保证的是类型中已经表达的条件,并不意味着业务规则、外部环境和所有执行结果都会自动正确。
1. Option 与 Result:显式表达状态的范围
Rust 的枚举是各变体可以包含不同数据的和类型。Option<T> 用 Some(T) 与 None 表示值的存在或缺失,Result<T, E> 用 Ok(T) 与 Err(E) 表示成功和失败。API 使用这些类型时,调用者可以从类型中看到缺失或失败的可能路径,并选择传播或分支处理。13
不能把这一优点扩大为“Rust 中不存在 null 或异常”的普遍命题。安全引用 &T、&mut T 以及 Box<T> 以指向有效值的非空指针为前提,但原始指针可以为空,FFI 与操作系统接口也可能传递空指针、错误码和外部异常。Option 不会自动净化这些边界;它只是把经过验证的缺失可能性表达为 Rust 类型的手段。13
未使用的 Result 受 #[must_use] 约束,但它默认属于 lint。可以降低 lint 级别,也可以用 let _ = 明确丢弃,因此编译器并不会为每个错误强制一种有意义的恢复策略。? 运算符通常也不是处理错误,而是把错误传播给当前函数的调用者。记录、重试、使用替代值还是终止,仍然属于 API 与应用程序设计问题。13
2. 模式匹配与穷尽性检查所保证的内容
match 会检查各分支合在一起是否覆盖目标类型当前可以构造的所有值。删除下面代码中的 None 分支会导致编译失败。
fn describe(value: Option<i32>) -> &'static str {
match value {
Some(number) if number > 0 => "正数",
Some(_) => "零或负数",
None => "没有值",
}
}
这一保证同样有边界。14
- 通配分支
_覆盖其余所有值,因此可以通过穷尽性检查,但它不会表明哪些变体得到了有意处理。API 新增变体时,现有通配分支可能静默吸收它。 - 模式守卫的条件可能为假,因此不能证明该模式匹配的所有值都已处理。上例中,
Some(number) if number > 0后仍需另一个Some分支,原因就在这里。 if let、let ... else、while let与matches!用于只关注部分模式,并不要求处理所有情况。- 来自其他 crate 的
#[non_exhaustive]枚举要求通配分支,以便未来增加变体。这有利于 API 演进,但也削弱了调用者通过列举当前全部变体、让新状态触发编译错误的能力。
因此,穷尽性检查保证的是:“根据当前类型定义和已写出的模式,没有遗漏情况。”它不证明每个分支的行为正确,也不证明通配分支会适当地处理新状态,或外部状态与类型定义一致。
3. 只有把不变式表达进类型,才能阻止无效状态
枚举、newtype、私有字段和经过验证的构造函数,可以让某些无效状态难以表达。例如,可以把经过范围验证的标识符或状态转换建模为单独类型,使使用安全公共 API 的代码无法绕过验证。
但开始时间必须早于结束时间、多个字段之间保持一致、文件实际存在、权限策略正确以及网络响应可信等关系,并不会只因类型名称而产生。必须把这些条件实现为构造函数和方法的不变式,并控制字段可见性和转换路径。一个值即使结构上类型正确,业务含义仍可能错误。
这一边界在 unsafe、FFI、反序列化和外部输入处尤其重要。把无效的枚举判别值或无效引用当作 Rust 值使用,可能导致未定义行为。外部字节、C 结构体、数据库行与网络消息,在作为 Rust 类型处理之前,必须验证长度、范围、编码、判别值以及字段间关系。类型系统的保证从有效类型值已经构造之后开始,并不会替代在边界建立该前提的责任。15
4. 运行时失败与控制流成本
Option 与 Result 把失败表示为值,并不意味着运行时失败消失。unwrap 与 expect 遇到非预期变体时会 panic,并根据构建的 panic 策略展开栈或中止进程。unwrap_unchecked 省去检查,但在错误变体上使用会触发未定义行为。16
按状态分支的代码会形成真实控制流。编译器可能把简单的 match 降低为条件分支、跳转表、条件移动或无分支代码,但具体结果取决于变体数量、数据布局、优化级别、目标 CPU 与周围代码。模式匹配或组合子本身并没有零成本保证。对高频路径,需要测量分支预测和错误值构造成本;把大型错误变体直接放入枚举,还是通过 Box 等方式间接存放,也是表示大小与分配成本之间的选择。
5. 表示方式、API 演进与维护成本
默认 repr(Rust) 枚举的确切字段顺序、判别值位置与整体大小,通常不是稳定 ABI。Option<&T> 等官方文档保证空指针优化的特定类型属于例外,但不能把一个案例的体积优化推广到所有 Option<T>、Result<T, E> 与用户枚举。若 FFI 或存储格式依赖布局,就需要合适的 repr 与单独的兼容性设计。16
把状态精细地划分为类型,可以让遗漏变成编译错误,并明确代码审查与测试目标。另一方面,变体和错误类型越多,match 分支、转换代码、文档与测试也会增加;通用错误层次和较长的组合子链可能使调试路径更复杂。给公共枚举增加变体可能破坏下游代码的穷尽匹配;#[non_exhaustive] 与通配符提高兼容性,却会削弱自动发现新状态的能力。类型精度、API 稳定性、诊断清晰度与维护成本必须一起设计。
阶段性结论
把视野从内存安全边界扩大后可以看到,Option、Result 与穷尽模式匹配,能够在缺失、失败和状态分支已表达为类型的代码中,于编译时阻止重要遗漏。但它们不会消除原始指针与 FFI 中的空值、panic、被忽略的错误、被通配分支吸收的新变体、错误业务规则和无效外部输入。能够阻止无效状态的范围,取决于不变式在类型与安全 API 中表达得多准确,以及它们是否在每个边界都得到保持。
从设计优点与成本来看,用类型细分状态,是把运行时的隐式失败转移到显式接口和编译器诊断中的有力机制。代价可能包括 API 设计、分支与表示大小、错误转换、兼容性、调试和维护成本。因此,类型系统与模式匹配的价值不在于“消除所有错误”,而在于明确选择要表达哪些错误状态,以及在哪些边界验证它们。
1.5 生态系统:Cargo 与 Crates.io
构建系统与包管理器
编程语言的采用不仅受语言自身特性影响,也受生态系统和工具影响。C、C++ 等部分系统编程语言没有官方指定的包管理器或构建系统,开发者有时不得不在不同项目中分别使用 Makefile、CMake 等工具并管理依赖。
Rust 在语言设计阶段就把提供开发环境工具设为目标之一。其成果是官方构建系统兼包管理器 Cargo,以及官方包仓库 Crates.io。
Cargo 是一个管理项目生命周期的命令行工具,其中包括代码编译。开发者可以通过命令完成以下工作。
- 创建项目(
cargo new): 创建具有标准化目录结构的新项目。 - 管理依赖: 在
Cargo.toml配置文件中注明所需库(Rust 中称为 crate)的名称和版本后,Cargo 会下载并管理这些库及其传递依赖。 - 构建与运行(
cargo build、cargo run): 通过命令编译和运行项目。 - 测试与文档(
cargo test、cargo doc): 执行项目中的测试代码,并根据源码注释生成 HTML 文档。
Crates.io 是一个集中式包仓库,类似于 Node.js 的 NPM 或 Python 的 PyPI。它为 Rust 开发者提供共享与使用库的平台。
Cargo 将项目配置、依赖管理、构建和测试流程集成到标准化工具中,旨在降低开发环境配置和依赖管理的负担。
2. Rust 的采用因素:技术、生态系统与叙事的互动
在众多语言不断出现与消失的编程语言市场中,Rust 在较短时间内赢得开发者偏好,并被主要技术企业采用。要理解这一现象,必须分析促成 Rust 采用的多种复合因素。
Rust 的采用难以用单一因素解释,可以看作技术背景、开发者体验、叙事和时代需求相互作用的结果。本章分析这些因素,考察 Rust 如何在软件开发生态系统中占据特定位置。
2.1 技术背景:内存安全与性能目标
Rust 采用的重要因素之一,在于它针对系统编程领域的目标——不降低性能的内存安全——所采取的技术方法。
C 和 C++ 提供硬件控制能力与性能,但处理内存错误属于开发者的责任。Java、C# 等基于垃圾收集器的语言提供内存安全,却因 GC 运行带来的运行时开销和暂停可能性,在操作系统、浏览器引擎等特定系统领域受到限制。
Rust 提出了不同于 C/C++ 和 GC 语言的方法。通过所有权和借用检查器这一编译时静态分析模型,它旨在不依赖 GC 防止内存错误,同时获得接近 C++ 的运行时性能。
这种方法提出了一种不同于“安全与性能必然相互权衡”传统看法的技术设计。在 Heartbleed 等安全事件之后,产业界对内存安全的需求上升,Rust 也在这一背景下受到关注。
2.2 开发者体验(DX):Cargo 与工具链
讨论 Rust 的采用过程时,一个重要因素是以官方构建系统和包管理器 Cargo 为核心的开发者体验(DX)。
C/C++ 生态系统使用 Makefile、CMake、autotools 等多种构建系统,且缺少标准化依赖管理方式;Rust 则从设计早期便提供统一工具链。开发者可以通过 cargo new、cargo build、cargo test 等命令完成项目创建、依赖管理、构建、测试与文档生成。
与 JavaScript 的 npm 或 Python 的 pip 类似,Cargo 成为推动 Rust 生态系统成长的基础设施。无论 Rust 的学习曲线如何,一部分开发者依据工具链对其生产力作出积极评价。
2.3 叙事建构与“议程设置”分析
技术的采用除了技术因素之外,还受到围绕技术形成的叙事与公众认知互动的影响。在 Rust 的案例中,可以观察到若干特定的叙事策略。
- 价值主张: “无畏并发”“不降低性能的安全”等口号展示了 Rust 试图解决的问题及其价值。
- “议程设置”分析: Rust 话语通过与 C/C++ 的比较,把内存安全突出为评价系统编程语言的标准。随着这一价值被带到讨论中心,内存安全成为主要评价标准。这可以分析为技术社区围绕特定价值塑造公众认知并设置议程的案例。
这些叙事为开发者学习和使用 Rust 提供了动力,也影响了社区内部身份的形成。
技术学习确实能够扩展知识和解决问题的策略。然而,掌握某种语言只是对该语言及其问题领域的学习成果,不是衡量一般智力或开发者整体能力的单一指标。如果不区分学习的效用与人的智力等级,说明技术教育价值的叙事可能转变为排斥非使用者的资格叙事。第 8.5 节和第 9.2 节将再次讨论这一问题。
2.4 机构支持与社区文化
Rust 从早期开始就得到 Mozilla 的支持。之后,由 Google、Microsoft、Amazon 等参与的 Rust Foundation 成立。这些机构与企业的支持有助于传播这样一种认识:Rust 是一个旨在解决产业问题的项目。
与此同时,Rust 项目正式采用行为准则,并强调欢迎新参与者的文化。The Rust Programming Language(通称 The Book)等官方文档被用作开发者学习资料,并影响了进入门槛。
2.5 采用因素综合
Rust 的采用可以视为上述多种因素相互作用的结果。
- 针对“不降低性能的安全”问题
- 提出了技术方法,
- 提供了包括
Cargo在内的开发者体验, - 通过叙事传达其价值,
- 并通过机构支持与社区建立生态系统基础。
理解这些多维采用因素,为评价本书其他章节讨论的 Rust 技术局限和话语问题提供了背景。
第二部分:主要设计原则的技术分析
第一部分考察了 Rust 的技术特性与相关叙事。第二部分将从技术上分析 Rust 的主要设计原则——安全性与所有权。
本部分从多个角度检视这些原则被评价为“创新”的依据、工程权衡,以及这些概念如何关联到 C++、Ada 等编程语言历史中的先例。同时,把现有代码库的重构、现代化、部分替换和全面重写区分为不同的变更策略,并建立分析标准,避免把语言层面的保证与项目层面的改善效果等同起来。
3. 对“安全性”叙事的多维分析
Rust 编程语言的身份建立在安全性这一主要属性之上。在 Rust 话语中,安全性被强调为解决 C/C++ 内存错误的主要特性。然而,“安全性”一词在技术、历史和话语语境中具有多层含义。
第 3 章旨在从多个角度分析这一安全性叙事。
首先,从历史语境检视被评价为“创新”的 Rust 核心概念与 C++、Ada 等先行技术之间的关系(第 3.1 节)。其次,明确界定 Rust 所保证的安全性的技术定义、边界(unsafe、panic)以及局限(内存泄漏、逻辑错误)(第 3.2 节)。第三,区分 C/C++ 内部的渐进改进与使用 Rust 重写,并比较各自提供的保证和转换风险(第 3.3 节)。第四,通过与 Ada/SPARK 和基于 GC 的语言比较,分析不同层次的安全保证与权衡(第 3.4–3.5 节)。最后,在这些技术分析基础上,检视安全性这一概念在话语中如何发挥作用(第 3.6 节),并以编程语言设计的权衡作结(第 3.7 节)。
3.1 “创新”的含义与历史先例分析
Rust 同时追求性能与安全,并为既有系统编程设计方式提出新的方法,因此被评价为创新。为了从工程与历史角度分析这种创新的含义,本节考察 Rust 核心概念所依据的技术先例。
软件工程的发展既来自对既有思想的继承,也来自新的应用。本节分析 Rust 的核心概念如何与 C++、Ada、函数式语言等领域发展出的思想相连接。
本节特别把 Ada 及其子集 SPARK 作为比较对象,因为 Ada/SPARK 是在 Rust 出现数十年前以不同方式追求“无 GC 安全性”的历史先例。因此,比较两种技术可作为分析工具,用于理解 Rust 方法在哪些方面具有原创性。
所有权与资源管理:继承 C++ 的 RAII 模式
Rust 的所有权模型与 C++ 中发展出的资源管理技术相关。C++ 确立了 RAII(资源获取即初始化)设计模式,把资源生命周期与对象生命周期关联,在调用析构函数时自动释放资源,并通过智能指针将其具体化。
通过资源“所有权”管理内存这一概念本身首先在 C++ 中确立。Rust 的特点在于,它把这一思想从选择性使用的模式变成了由编译器在整个语言中强制执行的规则。(第 4.1 节将继续详细分析 C++ 的 RAII 和智能指针。)
无 GC 的安全性:Ada/SPARK 的先例
Rust 的主要特性之一是“无垃圾收集器的内存安全”。这一目标更早就在 20 世纪 80 年代由美国国防部主导开发的 Ada 语言中得到追求。Ada 面向高可靠性系统设计,通过类型系统和运行时检查,在没有 GC 的情况下防止空指针访问、缓冲区溢出等错误。
Ada 的子集 SPARK 引入了形式化验证技术。17 这类技术用数学方法证明程序的特定属性,例如不存在运行时错误,其保证范围和可信程度不同于 Rust 借用检查器提供的内存安全保证。(第 3.4 节将继续详细比较。)
Rust 借用检查器与一般形式化验证之间存在一个实际差异:它以更自动化的方式处理内存安全问题。不过,“无 GC 实现安全”这一目标本身,在 Ada/SPARK 生态系统中已经有更早实现的历史先例。
明确的错误处理:函数式编程的影响
Rust 通过 Result 和 Option 进行明确错误处理的方式,也建立在既有编程范式之上。它借鉴了 Haskell、OCaml 等 ML 系函数式语言中发展出的代数数据类型(ADT)和单子式错误处理技术。这些语言长期通过类型系统明确表示“没有值”或“发生错误”的状态,并要求编译器强制处理所有情况。
概念的整合与强制执行
Rust 的核心概念并非彼此独立产生,而是整合既有语言思想的结果。C++ 的 RAII 原则、Ada/SPARK 对无 GC 安全性的追求,以及函数式语言基于类型的错误处理方式,都是其中的例子。
因此,Rust 的设计特点可以分析为:把多种概念整合到一种语言中,并由编译器将其作为语言基本规则加以强制执行,从而试图在广泛代码范围内提供安全保证。
3.2 Rust“安全性”的定义、边界与局限
Rust 的安全性并不意味着全面“无缺陷”,而是指一组定义明确的技术保证范围。理解这种安全性的准确含义和适用范围,是分析 Rust 工程设计所必需的。
第 3.2 节首先确定 Rust 所保证的安全性的核心定义(3.2.1),然后依次分析以 unsafe 关键字为代表的保证边界(3.2.2)、panic 失败模型(3.2.3),以及内存泄漏和逻辑错误等不属于保证范围的问题(3.2.4、3.2.5),从而明确其局限。
3.2.1 “安全性”的定义:防止未定义行为
在 Rust 话语中,安全性被提出为核心概念。这一术语需要明确的技术定义。在 Rust 的语言模型中,安全性并不意味着不存在所有类型的错误,而是以具体且有限的意义使用:保证不存在未定义行为(UB)。
在 C、C++ 等语言中,未定义行为是指程序进入语言规范未规定的状态后产生的不可预测行为,可能导致系统崩溃、数据损坏、安全漏洞等后果。
Rust 的设计目标之一,是在被归类为 Safe Rust 的代码范围内,于编译时静态阻止这种 UB。Rust 编译器,尤其是借用检查器,会阻止释放后使用、空指针解引用、缓冲区溢出以及线程间数据竞争等导致 UB 的原因。
Rust 官方文档 The Rustonomicon 对此作出明确说明:“当我们说代码是安全的时,我们作出一项承诺:这段代码不会表现出任何未定义行为。”18
因此,Rust 的安全保证集中在内存安全和线程安全(防止数据竞争)这些特定领域。这一技术定义与通常对“安全”的理解,如程序逻辑正确或不存在运行时错误,在范围上有所不同;它也构成理解后续局限(内存泄漏、panic 等)的基准。
3.2.2 unsafe 关键字与对 C ABI 的依赖
Rust 的编译时安全保证在被划分为 Safe Rust 的范围内有效。不过,Rust 通过 unsafe 关键字提供了明确绕过编译器规则(所有权、借用规则等)的路径。在 unsafe 块中,开发者可以执行解引用原始指针、访问可变静态变量等可能导致未定义行为的操作。unsafe 的存在界定了 Rust 安全保证的范围与边界。
unsafe 的主要用途之一是与外部语言互操作,即 FFI(Foreign Function Interface)。许多现代操作系统、硬件驱动和核心库把 C 语言 ABI(Application Binary Interface)作为事实上的标准接口。Rust 程序为了使用文件系统、网络、低层硬件控制等操作系统功能,往往必须调用通过 C ABI 实现的系统 API。
这些 FFI 调用要求使用 unsafe 块,因为 Rust 编译器无法验证 FFI 边界另一侧 C 代码的行为,例如传入的指针是否有效、缓冲区大小是否正确。因此,Rust 在与 C ABI 交互的地点存在结构性依赖;在这里,安全保证的责任从编译器转移到编写 unsafe 代码的开发者。
除 FFI 外,unsafe 还用于以下低层工作。
- 实现编译器无法验证的高性能数据结构,例如
Vec<T>内部的内存分配管理 - 在操作系统内核或嵌入式环境中直接控制硬件寄存器
Rust 生态系统通常把这些 unsafe 代码封装在安全接口之后。但是,如果 unsafe 实现存在缺陷,即使完全以 Safe Rust 编写的代码也可能发生内存错误。unsafe 既是 Rust 与包括 C ABI 在内的低层系统交互所必需的机制,也明确标示出 Rust 静态安全保证不再适用的边界。
3.2.3 “安全失败”与 panic 的含义
Rust 的错误处理模型包含“安全失败”这一概念,它与 panic 机制相关。为了分析 panic 的含义,可以从两个角度区分“失败”。
-
内存完整性角度(安全失败): 指以受控方式终止程序,区别于引发未定义行为或数据损坏的失败,例如 C/C++ 的段错误。Rust 的
panic默认展开栈,调用各对象的析构函数(drop),并在保持内存完整性的情况下终止线程。从这一角度看,panic不会导致 UB,因此属于安全失败。 -
服务连续性角度(不可恢复的中止): 指错误发生时,不通过异常处理等方式恢复逻辑或继续服务,而是终止相关线程。从这一角度看,
panic属于不可恢复的中止。
从技术上说,panic 保证内存完整性并有助于调试,但这与系统持续存活或服务韧性是不同的概念。
Rust 通过 std::panic::catch_unwind 提供一种路径,可阻止 panic 跨越线程边界传播并尝试恢复。19 它可以视为管理 panic 不可恢复中止特性的一种例外手段。
默认失败模式的比较:可用性与完整性的权衡
这种差异可以通过比较不同语言的默认失败模式来分析,尤其是开发者未实现错误处理时系统如何响应。
在 Java 或 C# 环境中,即使开发者省略异常处理,异常也会自动向上传播,并可能由框架层捕获,形成一种失效安全结构。这是一种以服务存活为中心的设计,降低未处理异常造成整个服务中止的可能性。
在 Rust 中,开发者可能不处理复杂的 Result,而选择 unwrap(),从而导致安全失败并中止执行。因此,当开发者选择阻力最小的路径时,Java 更可能继续服务,而 Rust 在结构上更可能中断服务。这表明 Rust 存在优先考虑数据完整性而非服务可用性的结构性倾向。
3.2.4 “安全的”内存泄漏问题
第 3.2.1 节所述 Rust 安全性定义聚焦于防止未定义行为,内存泄漏不属于这一保证范围。内存泄漏是指程序不释放已分配的内存,使系统可用内存逐渐减少。
从 Rust 的角度看,内存泄漏不是未定义行为,因此被归类为安全行为。未释放的内存可能导致程序资源耗尽,但不会像访问已释放内存或重复释放同一块内存那样造成内存损坏或系统崩溃。
即使在 Safe Rust 中也可能发生内存泄漏。一个例子是把引用计数智能指针 Rc<T> 与提供内部可变性的 RefCell<T> 一起使用时产生的引用环。
当两个或更多 Rc 实例通过 RefCell 等机制相互引用而形成环时,各实例的引用计数无法降为零。即使程序其他部分已无法访问这个循环结构,环内部的计数仍不为零,因此不会调用析构函数(drop),相关内存也不会释放。
这是发生在安全代码中的逻辑问题,它并未违反 Rust 的所有权或借用规则。这表明 Rust 的安全模型不会自动解决所有类型的内存相关问题。
3.2.5 保证范围之外的问题:逻辑错误、死锁等
如第 3.2.1 节所定义,Rust 编译器的安全保证集中于内存安全(防止 UB)和防止数据竞争这些特定领域。编译器不会防止超出这一范围的所有类型错误。 以下主要问题类型不属于 Rust 的安全保证范围,仍由开发者负责。
-
逻辑错误 程序逻辑本身可能与预期不同。例如,在金融计算中使用错误利率,或重复执行折扣逻辑。Rust 借用检查器验证内存访问是否有效,却不会验证业务逻辑是否“正确”运行。
-
死锁 Rust 的并发保证会防止多个线程同时写入同一数据而产生的数据竞争,但不能防止两个或更多线程分别持有不同资源(例如互斥锁 A、B),并无限等待对方资源(B、A)的死锁。这是并发设计的逻辑缺陷,而不是内存安全问题。
-
整数溢出 当运算超过整数类型可表示范围时会发生整数溢出。Rust 在调试构建中触发
panic,但在发布构建中默认让数值回绕。这不是未定义行为,但若开发者不明确处理,可能导致计算错误或逻辑缺陷。 -
资源耗尽 除第 3.2.4 节的内存泄漏外,逻辑错误还可能使文件句柄、网络套接字、数据库连接等有限系统资源无法释放。Rust 的 RAII 模式(
Droptrait)有助于释放资源,但语言层面并不保证不存在所有类型的资源泄漏。
2024 年发现的 CVE-2024-24576 可以说明这种保证范围的局限。该漏洞发生在 Rust 的安全标准库 API std::process::Command 中,CVSS 评分为 10.0(Critical)。其原因并非内存错误,而是在 Windows 环境处理命令时未正确转义参数而产生的命令注入漏洞,也就是逻辑错误(CWE-78)。
这一案例表明,即使 Rust 防止了与内存有关的 UB,在保证范围之外仍可能出现逻辑型安全漏洞。
3.3 比较分析 1:C/C++ 的多层安全保障与变更策略
有关 Rust 安全性的讨论经常通过与 C/C++ 比较来说明其价值。如果只把 20 世纪 90 年代的 C/C++ 固定为比较对象,或者排除改进现有代码的可能性,只给出“保持现状”和“用 Rust 全面重写”两个选择,就会缩小实际工程选项。比较对象必须同时包括现代 C/C++ 语言和工具,以及改变代码库的多种策略。
3.3.1 区分重构、现代化与重写
在软件工程中,以下工作彼此相关,但并不相同。
- 重构: 在保持外部可观察行为的同时改善代码内部结构。例如拆分函数、依赖倒置、明确所有权边界、消除重复、减少全局状态。20
- 现代化: 一个更广泛的概念,包括采用新的语言标准和库、更换构建系统、自动化静态分析和测试,以及改变 API 和架构。必要时也可以有意改变外部行为或运行方式。
- 重写或语言迁移: 用新实现替换现有实现。由于需要重新解释需求并重新实现行为,程序的同一性不会像狭义重构那样自动保存。
因此,“用 Rust 重写 C/C++”是语言迁移或重新开发策略,并不是严格术语意义上的 C/C++ 代码重构。 如果用同一个词称呼两种工作,就无法区分内部结构改善的效果与更换语言所提供的保证。
3.3.2 C/C++ 内部重构与现代化的实际价值
C/C++ 不像 Rust 那样提供一个强制内存安全的统一默认规则,但不能由此得出同一语言内部的改进毫无意义。风险不仅是离散的“安全/不安全”状态;缺陷频率、影响范围、发现时间和修复成本都是连续变化的工程变量。
语言与库层面的改进
- C++ 可以通过 RAII 和
std::unique_ptr统一所有权,并使用std::vector、std::array、std::span、std::string_view等更明确表达长度与生命周期信息的类型,缩小原始指针和手工缓冲区操作的范围。 - C 可以在 API 中明确分配器与释放器的对应关系,采用携带长度的缓冲区接口、单一清理路径、不可变数据与只读视图,以及错误返回约定,使所有权和失败处理更容易验证。
- 两种语言都可以把危险的低层操作隔离到小型模块中,对外提供更窄、更容易验证的接口。
工具与验证层面的改进
- 静态分析: Coverity、PVS-Studio、Clang Static Analyzer、Clang-Tidy 等工具检测潜在缺陷和违反 C++ Core Guidelines21 等规则的情况。
- 动态分析: AddressSanitizer、UndefinedBehaviorSanitizer、ThreadSanitizer、Valgrind 等工具在执行期间检测内存访问与并发错误。
- 测试与模糊测试: 回归测试、基于属性的测试和模糊测试可以把现有实现的实际行为规范化,并检测变更过程中行为的偏离。
- 高可信方法: MISRA C/C++、受限语言子集、代码审查,以及 Polyspace、Frama-C 等静态验证工具,对允许的语言功能和缺陷类型施加更严格的限制。
这些改进的效果与局限可以区分如下。
| 改进对象 | 重构或现代化方法 | 预期效果 | 剩余局限 |
|---|---|---|---|
| 所有权不明确的资源 | RAII、智能指针、明确的 C 所有权约定 | 减少泄漏、二次释放、释放后使用的可能性 | 绕过规则的代码和外部 API 仍需单独验证 |
| 原始缓冲区与指针运算 | 容器、携带长度的接口、范围视图 | 减少越界和长度不一致的可能性 | 可能无法在所有调用点强制执行 |
| 全局状态与共享可变状态 | 封装、单写者、明确锁顺序、消息传递 | 缩小数据竞争范围,提高可测试性 | 死锁和逻辑并发错误仍可能存在 |
| 复杂模块与宽接口 | 模块拆分、明确不变量、适配层 | 减少变更影响范围和认知复杂度 | 不等同于语言层面的正确性证明 |
| 发现潜在缺陷 | 静态与动态分析、测试、模糊测试 | 提高缺陷发现率,提前发现回归 | 依赖分析精度和测试覆盖率 |
这些方法并不适用于所有项目,一些代码库仍继续使用旧标准和旧实践。22 它们也不提供与 Rust 借用检查器相同的保证。然而,不同并不意味着无效。 改善代码可变更性、缺陷密度、故障影响范围与可验证性,本身具有独立的工程价值。
3.3.3 Rust 重写新增的保证与新产生的风险
使用 Rust 重写会在 Safe Rust 范围内强制所有权和借用规则,从而在编译时阻止释放后使用、数据竞争等特定缺陷类型。对于难以在整个项目中一致应用安全规则的组织,这种强制默认值可能是重大优势。
但是,重写不只是从旧代码中消除缺陷,还要用新实现重新建立既有行为。这个过程会产生以下风险。
- 隐式规范的丢失: 旧代码可能积累了未记录的异常处理、兼容规则、性能特殊处理和运行经验。重写时遗漏这些内容,会产生与内存安全无关的功能回归。
- 已验证行为的重置: 用新代码替换长期运行并修正过的实现后,新实现必须重新经历故障和边界条件才能成熟。
- 过渡期复杂性: 可能需要旧实现和新实现并行运行、数据格式转换、部署与回滚,以及重复设置可观测指标。
- 语言边界风险: 在连接 C ABI、操作系统 API 和既有库的
unsafe或 FFI 边界上,Rust 的保证不会自动扩展。 - 新的维护成本: 组织必须承担 Rust 人员、crate 稳定性、构建时间、调试工具以及两种语言长期共存的成本。
因此,更换语言不是简单消除风险,而是重新分配风险的种类与位置。内存安全风险可能下降,但与规范恢复、集成、运行和组织能力相关的风险可能上升。
3.3.4 变更策略的连续谱与选择性采用
实际项目的选择连续分布在“保持现有代码不变”和“全部用 Rust 重写”之间。
| 变更策略 | 适用情况 | 主要优势 | 主要风险 |
|---|---|---|---|
| 保留现有实现并修补缺陷 | 变更成本非常高,缺陷局部集中 | 最小的过渡风险 | 结构性债务可能持续 |
| 在 C/C++ 内部现代化 | 保留行为资产,同时提高结构和验证水平 | 渐进部署与回归控制 | 安全规则的一致性依赖组织纪律 |
| 选择性用 Rust 替换高风险模块 | 内存漏洞集中在接口明确的特定边界 | 把保证集中到高风险区域 | FFI 和双语言维护成本 |
| 用 Rust 编写新组件 | 新功能需要保留的既有行为较少 | 无重写回归即可利用 Rust 默认值 | 需要与既有系统集成 |
| 全面重写 | 既有结构无法满足需求,且行为规范和过渡资源充足 | 同时重新设计架构与语言模型 | 成本、进度、功能回归和过渡失败风险最高 |
在渐进策略中,可以先用回归测试固定现有行为,缩小危险接口,试验性地替换代表性模块,然后比较缺陷率、性能、运行复杂度和维护成本。如果 Rust 产生更好结果,就扩大采用范围;否则可以保留既有实现或选择其他方法。
3.3.5 主张分析:“唯一有意义的重构是用 Rust 重写”
下面的主张忽略了上述区分。
“C/C++ 的重构就是用 Rust 重写,除此之外的重构毫无意义。”
这一主张存在以下逻辑与工程问题。
- 范畴错误与偷换概念: 把保守地改善内部结构的重构,与更换实现语言的重写视为同一种工作。
- 虚假二分: 只保留放任既有代码和用 Rust 全面重写两种选择,排除现代化、隔离、部分替换和强化验证等中间策略。
- 完美主义谬误(nirvana fallacy): 因为 C/C++ 改进无法从源头阻止所有内存错误,就把其局部且可测量的效果评价为零。这是以理想的完整解法为标准,否定现实改进方案。
- 归约为单一指标: 用内存安全代替软件质量的全部维度。逻辑正确性、可用性、性能、兼容性、验证资产、运行风险和维护成本都需要分别评价。
- 遗漏过渡风险: 只计算新语言的优点,却把重写产生的功能回归、隐含规范丢失、FFI、双重运行和组织学习成本排除在成本函数之外。
更准确的命题如下。
当内存生命周期与并发缺陷是主要风险,目标模块边界与行为规范明确,并且组织具备长期维护 Rust 及过渡期的能力时,用 Rust 重写可以成为强有力的替代方案。然而,C/C++ 内部的重构和现代化同样能够实质改善缺陷概率、变更成本、故障影响范围与可维护性;两种策略的价值应通过可测量结果进行比较。
总之,Rust 的编译器内建安全功能与 C/C++ 的多层方法相比,差异在于把安全性作为强制默认值提供。这是一个重要优点,但不会使既有代码改进的价值归零,也不会自动证明全面重写合理。
3.4 比较分析 2:Ada/SPARK 的数学证明与保证层级
本节把 Ada 及其子集 SPARK 作为分析工具,描述 Rust 的安全模型位于系统编程安全保证谱系中的什么位置。通过与 SPARK 的“数学证明正确性”比较,探讨 Rust 模型的工程特征与保证范围。该比较旨在理解不同设计哲学所选择的权衡。
Rust 的安全保证:防止未定义行为(UB)
如第 3.2 节所分析,Rust 的核心安全保证是通过所有权与借用规则,在编译时防止会导致未定义行为(Undefined Behavior, UB)的内存访问错误和数据竞争。
但是,这一保证并不确保程序的逻辑正确性,也不保证所有类型的运行时错误都不存在。例如整数溢出和数组索引越界可能导致 panic(第 3.2.3 节),这与保证系统稳定“运行”是不同的。
Ada/SPARK 的安全保证:证明程序正确性
相比之下,Ada/SPARK 生态系统以更广泛的正确性为目标。
-
Ada 的基本安全性与恢复力: Ada 在语言层面通过类型系统和契约式设计(Design by Contract)尝试防止逻辑错误,并且默认在发生包括整数溢出在内的运行时错误时抛出异常。这种设计以错误处理例程维持系统任务为目标,追求恢复力(resilience)。
-
SPARK 的数学证明: Ada 的子集 SPARK 使用形式化验证工具,从数学上分析代码的逻辑属性。由此可以在编译时证明不会发生运行时错误,包括整数溢出和数组索引越界。
错误处理设计的差异:恢复与中断
这些技术差异源于处理错误的不同设计哲学。
- Ada: 把运行时错误视为异常,支持系统恢复(recover)。这反映了重视可用性的任务关键型系统的要求:即使发生错误,整个系统也应保持可用状态。
- Rust: 把同一错误视为程序的缺陷,并通过
panic中断相应执行流。这是为了避免在错误状态下继续执行可能引发的二次问题(如内存破坏),优先强调内存安全与完整性的设计。
这一差异不只是功能有无,而是体现了两种语言针对不同系统性质所设定的设计目标。
两种语言的保证层级比较
| 错误类型 | Rust | Ada(基本) | SPARK |
|---|---|---|---|
| 内存错误(UB) | 编译时阻止(保证) | 编译/运行时阻止(保证) | 以数学方式证明不存在 |
| 数据竞争 | 编译时阻止(保证) | 运行时阻止(保证) | 以数学方式证明不存在 |
| 整数溢出 | panic(调试)/ 回绕(发布) |
运行时异常(可恢复) | 以数学方式证明不存在 |
| 数组越界 | panic(不可恢复中断) |
运行时异常(可恢复) | 以数学方式证明不存在 |
| 逻辑错误 | 程序员责任 | 通过契约式设计部分防止 | 可根据契约证明不存在 |
结论:在安全谱系中的位置
这一比较表明,Rust 的安全模型位于“安全性”谱系的特定位置。SPARK 为获得数学证明,要求开发者明确投入证明工作(注解、契约说明等)并使用专业工具;Rust 则集中于较窄的保证范围(防止 UB),以开发者的学习曲线(借用检查器)为成本,提供自动化安全。
两种技术针对不同的工程问题提出解决方案。因此,只以 C/C++ 为比较对象评价 Rust 的安全性,可能无法呈现系统编程保证的完整谱系。
3.5 比较分析 3:重新评价替代性内存管理方式(GC)
Rust 的内存管理方式经常与 C/C++ 的手动管理比较。然而,系统编程谱系也包括通过垃圾回收器(GC)实现内存安全与生产力的语言,如 Go、C# 和 Java。
部分 Rust 相关话语依据 GC 的“Stop-the-World”暂停和运行时开销,主张 GC 语言不适合某些系统编程领域。这种说法或许适用于过去的 GC 技术,却可能没有反映近年的 GC 特征。
当前主流语言所采用的 GC 通过分代(generational)、并发(concurrent)和并行(parallel)GC等技术,在尽量减少应用程序中断的同时管理内存。例如,Go 的 GC 以微秒(µs)级暂停为设计目标,并用于网络服务器和云基础设施。Java 的 ZGC 与 Shenandoah GC 即使面对大容量堆,也以毫秒(ms)级暂停为目标。
Rust 的所有权模型与 GC 可以被理解为对“成本在哪里支付”采取不同设计哲学。
- Rust 的方式: 尽量减少运行时成本,但把一部分成本转移到编译时间和开发者认知负担(学习曲线、借用检查器),也就是以开发时间支付。
- GC 语言的方式: 减少开发者认知负担和开发时间,但在运行时以 CPU 和内存资源支付,即使用机器时间作为成本。
在资源受限的嵌入式系统或硬实时操作系统等领域,GC 的使用确实受到限制。但是,把这些特定要求一般化并据此评价所有 GC 语言的实用性,可能忽视多样化商业环境的需求。在部分商业环境中,开发速度和上市时间可能比极致运行时性能更重要,此时 GC 语言可以成为合理选项。
3.6 话语分析:“实用性”与“责任”的重新定义
前述第 3.1–3.5 节从技术与历史角度分析了 Rust 的安全模型,并与 C++、Ada/SPARK 和 GC 语言等其他方式进行了比较。过程中也讨论了 Rust 的概念先例(3.1)与技术限制(3.2)。
第 3.6 节把分析重点从技术事实移向技术话语:这些事实如何在 Rust 生态系统中被沟通和解释,以及“安全性”这一核心叙事如何被维持和防御。
首先考察“创新”的含义如何被重新定义为“实用性”(3.6.1),然后分析面对内存泄漏或 unsafe 缺陷等技术限制时,“责任”如何被归属(3.6.2)。
3.6.1 “实用创新”的话语功能
第 3.1 节分析了 Rust 的核心概念如何建立在 C++、Ada 等先行技术之上。对此,有观点主张 Rust 的创新不在于“发明概念”,而在于“价值的大众化(democratization)”或“实用创新”。
其论证如下:Ada/SPARK 的“无 GC 安全性”在航空、国防等特定领域要求较高成本(学习曲线、专业工具、开发速度),因此未能普及到普通开发者。相比之下,Rust 通过 Cargo 等工具生态和社区,把这一概念扩展到一般系统编程领域。也就是说,多数人能够运用的技术,比只有少数人使用的技术具有更大的工程意义。
本书所分析的是“实用创新”这一主张在技术话语中的运作方式。当它被用来回应“缺乏概念原创性”的批判性问题时,往往会发挥修辞工具的作用。
对于“概念 A 是否新颖?”这一问题,以“A 已在市场中使用且很实用”作答,可能并未直接回答前者。它把讨论范畴从概念的起源转向实用效用,可以看作一种话题转移。
这种逻辑转换可能进一步形成以 Rust 的“实用成果”为依据、暗示其“概念唯一性”的话语。其结果可能是 Ada、C++ 等语言的历史与工程成果被相对低估或排除在讨论之外。“实用创新”能够解释 Rust 的成绩,同时也可能发挥回避批判性审查“创新”原义的话语功能。
3.6.2 “责任”的归属:内存泄漏与 unsafe 的讨论方式
在讨论 Rust 的技术限制(第 3.2 节)时,相关问题的“责任”归属往往呈现特定话语模式。它可以被分析为一种逻辑边界划定,用以保存该语言“安全性”这一核心概念。
1. 内存泄漏:通过安全定义分离责任
如第 3.2.4 节所分析,Rust 中即使是安全代码,也可能因循环引用等机制产生内存泄漏。
当这一技术事实被提出作为对 Rust 内存安全的批评时,相关话语往往引用第 3.2.1 节的技术定义,即“安全性等于防止 UB”。由于内存泄漏不会导致未定义行为,因此按此逻辑,它不是“不安全”行为,也不属于编译器安全保证的范围。
这种方式把“内存问题”分成“导致 UB 的问题”和“不导致 UB 的安全逻辑问题(内存泄漏)”。结果是,防止内存泄漏的责任从编译器保证领域转移到开发者的逻辑责任领域。这与 C/C++ 社区把内存管理更广泛地视为开发者责任的方式有所不同。
2. unsafe 缺陷:通过 unsafe 边界隔离责任
如第 3.2.2 节所述,unsafe 块内的代码绕过编译器的部分安全检查,其中的缺陷甚至可能损害以 Safe Rust 编写的代码。
当库的 unsafe 代码中发生内存错误时,话语往往强调 Safe Rust 本身的保证并未失败。错误原因不归于 Safe Rust 模型,而归于编写 unsafe 代码的开发者。
unsafe 关键字一方面明确标示某段代码需要被信任,另一方面也把该区域中问题的责任隔离给开发者。这与 C/C++ 中库缺陷可能被视为语言自身固有风险的体现形成对照。
总的来说,这两种讨论方式构成了维持 Rust 核心命题——Safe Rust 保证内存安全——的机制。通过(1)把安全定义限定为防止 UB,以及(2)借助明确的 unsafe 边界分离责任,即使生态系统中实际发生内存泄漏和 unsafe 实现缺陷,相关话语仍维持 Safe Rust 保证有效这一核心叙事。
3.7 结论:性能、安全性与生产力的权衡
在软件工程中,单一工具很难满足所有要求。编程语言设计同样如此。工程设计通常是协调多个目标之间权衡关系的过程。
编程语言的设计方向通常围绕三个因素确定:性能与内存控制、开发生产力以及编译器层级的安全性。每种语言及其生态系统在三者之间选择不同位置,因此具有不同特征和成本。
- C/C++: 优先考虑硬件控制和执行性能。开发者必须直接承担包括内存管理在内的责任(第 3.3 节),安全性依赖外部工具和纪律。
- Go、Java/C#: 通过垃圾回收器和运行时强调开发生产力(第 3.5 节),以运行时开销支付这一设计的成本。
- Ada/SPARK: 以数学可证明的最高层级安全性与正确性为目标(第 3.4 节),要求较高开发成本和专业能力。
- Rust: 目标是在没有 GC 的情况下,同时实现接近 C++ 的性能和“内存安全(防止 UB)”(第 3.2 节)。它不支付运行时成本,而要求开发者学习并应用所有权和借用检查器模型,承担开发时间和认知成本。
由于这些设计差异,各语言可能适合不同开发场景。例如,Web 服务后端可能选择 Go 的生产力,航空器控制系统可能选择 SPARK 的可证明保证,而不适合使用 GC 的系统可能选择 Rust 模型。
总之,“安全性”不是单一概念,而是多层谱系(参见第 3.4 节表格);每种语言都按照自身设计目标拥有特定特征及相应成本。因此,分析具体问题领域的限制与需求,并选择合适工具,才是工程方法。
4. 重新评价所有权模型及其设计哲学
首先,第 4.1 节分析这一概念如何起源于 C++ 的 RAII 模式和智能指针。接着,第 4.2 节分析 Rust 的特点在于编译器把 C++ 的“可选模式”转化为“强制规则”。最后,第 4.3 节与 Ada/SPARK 的契约式设计比较,考察所有权模型在实现特定数据结构时产生的权衡。
4.1 所有权概念的起源:C++ 的 RAII 模式与智能指针
为理解 Rust 所有权模型的历史背景,可以考察资源管理在 C/C++ 中如何演化。
C 语言的手动内存管理及其限制
C 语言通过 malloc() 和 free() 赋予程序员对动态内存的控制权。这种设计提供灵活性和性能,但也要求程序员负责在适当时机对每次分配恰好释放一次。
手动管理模型发生错误时,可能导致以下内存问题。
- 内存泄漏: 已分配内存未被释放,导致可用内存减少。
- 二次释放: 再次释放已经释放的内存,破坏内存管理器的状态。
- 释放后使用: 访问已经释放的内存区域,可能造成数据损坏或安全漏洞。
由于这些问题,C++ 开始探索不只依赖程序员个人责任、而是系统性解决资源管理的范式。
C++ 的发展:RAII 模式与智能指针
C++ 为把资源管理责任从程序员个人转移到语言的对象生命周期规则,引入了 RAII(Resource Acquisition Is Initialization)模式。RAII 在对象构造函数中取得资源,在析构函数中释放资源。C++ 编译器保证对象离开作用域时调用析构函数,包括正常返回与异常展开,因此可以防止遗漏资源释放。
把 RAII 应用于动态内存管理的代表是智能指针。C++11 起标准化的智能指针与 Rust 所有权模型具有相似性。
std::unique_ptr(唯一所有权): 表达对特定资源的独占所有权。禁止复制、只允许通过移动转移所有权,这与 Rust 的默认所有权模型和移动语义相连。std::shared_ptr(共享所有权): 通过引用计数让多个指针共同拥有一个资源。这是 RustRc<T>和Arc<T>的概念基础。
C++ 通过 RAII 和智能指针确立了“资源所有权”概念,并提供了处理该概念的实际机制。
4.2 Rust 所有权模型:不是“发明概念”,而是“编译器强制”
第 4.1 节分析了 Rust 所有权概念与 C++ RAII 模式及智能指针的关联。Rust 的特点不在于发明概念本身,而在于从语言层面强制执行既有所有权原则的方式。
从可选模式转变为强制规则
在 C++ 中,使用 std::unique_ptr 等智能指针属于设计模式,是开发者的选择。开发者可以不遵循该模式而使用原始指针,编译器不会阻止。确保安全的责任仍由开发者承担。
相反,Rust 把所有权规则设定为内建于类型系统的强制规则,而不是可选模式。所有值都必须遵守这些规则,名为借用检查器的静态分析组件在编译时验证是否合规。除非使用 unsafe 块,否则违反规则会成为编译错误并阻止程序生成。
这种设计把安全保证的主体从开发者转移到编译器静态分析,因此与 C++ 不同。不过,也需要考虑依赖工具对运行时安全实践产生的影响。
在 C 环境中,对代码潜在危险的认识往往会促使开发者采取防御式编程。相反,对编译器安全保证的信任可能降低对运行时逻辑错误或异常情况的防御意识。例如,不明确处理 Result,而选择 unwrap(),可以被理解为基于语言提供的安全网优先考虑便利性。
从熟练开发者视角看权衡
对 C/C++ 开发者而言,“编译器强制”同时具有实用性和约束性。
部分 C/C++ 开发者可以认识到 Rust 的所有权规则与既有最佳实践一致。
- Rust 的移动语义类似于使用 C++
std::unique_ptr和std::move转移所有权的模式。 - Rust 的不可变引用(
&T)和可变引用(&mut T),与 C++ 使用const T&保证数据不变性或阻止并发修改的设计原则处于相同语境。
从这一角度,Rust 可以被评价为由编译器明确强制原本“隐含纪律”的工具。
但这种强制性也可能成为限制。在实现特定数据结构或进行性能优化时,开发者可能使用超出借用检查器分析能力的内存管理模式。由于借用检查器无法证明所有有效程序,逻辑上安全的代码可能仅因“编译器无法证明”而被拒绝。
因此,Rust 所有权模型通过强制规则提高代码安全层级;同时,由于优先固定规则的设计哲学,它也包含在特定情况下限制开发灵活性的权衡。
4.3 设计哲学比较:所有权模型与契约式设计
编程语言为保证正确性采用不同设计哲学。Rust 的所有权和借用模型专注于在编译时自动防止特定类型错误。Ada/SPARK 使用的契约式设计则由工具验证开发者明确给出的逻辑契约。
为分析两种哲学的差异及各自的工程权衡,本节以计算机科学中的双向链表实现作为案例研究。
1. 方法 1:Rust 的所有权模型
双向链表中,每个节点都相互引用前一个和后一个节点。在其他语言中可以直接通过指针或引用实现的这一结构,与 Rust 默认规则直接冲突,因为 Rust 所有权系统通常不允许循环引用,也不允许对同一数据存在多个可变引用。
因此,试图用引用直接表达这一结构的节点定义,会被借用检查器作为编译错误拒绝。
// 无法编译的代码
struct Node<'a> {
value: i32,
prev: Option<&'a Node<'a>>,
next: Option<&'a Node<'a>>,
}
要在安全 Rust 中解决这一限制,必须组合使用语言提供的特定机制:用 Rc<T> 实现共享所有权,用 RefCell<T> 实现内部可变性,并用 Weak<T> 打破循环引用。
// 使用 Rc、RefCell 和 Weak 的实现示例
use std::rc::{Rc, Weak};
use std::cell::RefCell;
type Link<T> = Option<Rc<Node<T>>>;
struct Node<T> {
value: T,
next: RefCell<Link<T>>,
prev: RefCell<Option<Weak<Node<T>>>>,
}
- 分析: 这种方式的优点是编译器能够自动防止数据竞争等特定类型的并发问题。所有权规则强制执行特定内存安全约束;在双向链表这类需要共享状态的场景中,它促使开发者通过
Rc、RefCell等机制明确处理该状态。由此产生的认知成本与代码冗长,是这一设计哲学的代价。开发者的注意力可能从问题的逻辑结构转移到如何满足编译器规则。
2. 方法 2:Ada/SPARK 的指针与契约式设计
Ada 通过 access 类型支持类似 C/C++ 的指针,并能够表达双向链表结构。
-- 使用 Ada 的表达
type Node;
type Node_Access is access all Node;
type Node is record
value : Integer;
prev : Node_Access;
next : Node_Access;
end record;
默认情况下,Ada 会在运行时检查空 access 值解引用等错误,并抛出 Constraint_Error 异常,从而提供安全性。
更进一步,Ada 的子集 SPARK 通过契约式设计提供一种在编译时以数学方式证明运行时错误不存在的方法。开发者为过程或函数声明前置条件(Pre)与后置条件(Post),静态分析工具则验证代码是否始终满足这些契约。
-- 通过 SPARK 契约证明安全性的示例
procedure Process_Node (Item : in Node_Access)
with Pre => Item /= null; -- 声明“Item 不为 null”的契约
- 分析: 这种方式让开发者通过类似 C/C++ 的指针模型表达数据结构。安全性由运行时检查,或由开发者亲自编写的明确契约与静态分析工具的证明来获得。这一设计哲学的成本,是开发者必须考虑所有潜在错误路径并把它们形式化为契约的责任与工作量。如果契约遗漏或编写错误,安全保证可能不完整,这带来一种不同于自动规则方式的风险。
3. 设计哲学比较与结论
两种方式把确保软件正确性的责任与成本分配给不同主体和阶段。
| 维度 | Rust | Ada/SPARK |
|---|---|---|
| 提供安全的主体 | 编译器(自动强制隐含规则) | 开发者 + 工具(编写明确契约并进行静态证明) |
| 默认范式 | 默认限制,按需引入复杂性 | 默认允许,通过按需安全证明施加约束 |
| 主要成本 | 实现特定模式时的认知负担和代码复杂度 | 需要为所有交互编写形式化规范 |
| 主要优势 | 自动防止数据竞争等特定错误类别 | 直接表达开发者设计意图,并可证明广泛逻辑属性 |
因此,Rust 所有权模型不应仅以“创新”或“缺陷”的二分视角评价,而应作为一种具有优点及相应成本的设计哲学来分析。它能够预防特定类型的缺陷,同时要求开发者付出学习成本,并采用特定的问题解决方式。语言是否适合,取决于待解决问题的类型、团队能力,以及项目优先考虑的价值,例如自动化安全保证还是设计灵活性。
第三部分:生态系统现实与结构性成本
第三部分分析 Rust 生态系统面临的实际挑战及其背后的结构性成本。在评价 Rust 的开发者体验、零成本抽象原则和实际工业应用限制时,有必要根据性质把问题分成两类。
-
成熟度问题: 库不足、部分工具不稳定、文档不完善等问题,可能随着时间和社区投入的积累而自然解决或缓解。这是所有成长中的技术生态系统都会经历的成熟度问题。
-
设计内在的权衡: 为实现语言的核心价值,例如运行时性能和无 GC 的内存安全,而有意牺牲其他价值,例如易学性、编译速度或实现特定模式的灵活性。它们是“选择”而不是“缺陷”,因此很难仅随时间推移而消失。
后续章节将以这一分析框架为基础,明确区分并评价 Rust 各项技术挑战属于哪种问题。
5. 开发者体验的成果与成本
第 5 章分析使用 Rust 的开发者所经历的多个开发者体验维度及相应成本。
讨论从借用检查器和学习曲线对生产力的影响开始(5.1),接着考察技术选择的普遍化倾向(5.2)。然后分析异步编程(5.3)和错误处理模型(5.4)等具体技术领域的复杂性与权衡。最后,通过分析库生态系统(5.5)和开发工具链(5.6、5.7)的挑战,结束对开发者体验的讨论。
5.1 借用检查器、学习曲线与生产力权衡
实现 Rust 安全模型的核心机制是借用检查器,它在编译时静态强制所有权、借用和生命周期规则。其严格性与开发生产力形成权衡。习惯其他编程范式的开发者必须重构既有思维方式以适应 Rust 模型,从而形成学习曲线。
权衡的两面:学习成本与安全保障
借用检查器施加的规则在开发过程中产生认知成本,同时也从根源上防止特定类型的运行时错误。
-
所有权与借用模型的成本和收益: 开发者必须对每个值应用单一所有者规则,并在访问数据时遵守不可变或可变借用规则。除实现逻辑外,这可能要求额外工作以满足编译器规则。作为回报,编译器在编译时防止数据竞争等并发问题,并消除释放后使用等内存错误的可能性。
-
明确生命周期的成本和收益: 当编译器无法自动推断引用有效性时,开发者必须直接声明
'a等生命周期参数。这要求额外的抽象思考以通过静态分析。但明确标注使编译器能够静态验证并阻止悬垂指针等对无效内存的引用。 -
特定设计模式的实现限制与替代方案: 借用检查器的分析模型,使双向链表或需要循环引用的图结构等难以仅用默认规则实现。这表明借用检查器模型能够表达的程序范围存在限制。在此类情况下,开发者可以使用
Rc<T>、RefCell<T>或unsafe块,明确处理规则例外并实现所需数据结构。
对生产力的影响及相关话语
这些技术特征会影响项目生产力。开发团队加入新成员时,可能产生适应期和培训成本,导致初期生产力下降。功能实现也可能因解决编译错误而延迟,降低项目进度的可预测性。在把开发时间视为资源的商业环境中,这些构成成本与风险。
这种学习曲线是为实现“无运行时性能损失的安全性”而选择的设计权衡之一。在部分网络讨论中,也能观察到把学习困难重新解释为增强开发者能力或衡量专业水平的观点。批评者认为,把学习过程的困难归结为个人能力问题,可能成为新开发者的进入壁垒,并限制关于改进工具可用性的讨论。
5.2 技术选择的普遍化倾向与工程权衡
新技术出现时,人们往往试图把应用范围扩展到其原始目的之外。这种现象被称为工具定律(law of the instrument),可以理解为技术采用过程中的一般社会心理动力。
Rust 为分析这一倾向提供了案例。语言提供的内存安全价值,以及掌握它所需的学习时间,使开发者对该技术投入大量精力。这种投入可能进一步推动开发者试图把其用途从特定领域扩展到更广范围。
本节分析这种普遍化在 Rust 相关讨论中的两个方面。第一,考察评价其他语言时,把 Rust 的主要特征(如无 GC、运行时性能)作为排他性标准的倾向。第二,通过普通 Web 应用开发案例,考察在考虑问题特征与约束后,权衡分析会如何不同。
与其他技术比较时的偏向
技术选择的普遍化倾向可能给与其他语言的比较带来特定偏向。
Rust 的“无 GC 内存安全”和“高运行时性能”有时被当作评价技术的主要标准。在这种视角下,其他语言可能被如下评价。
- C/C++: 缺乏强制内存安全成为主要评价依据,压过生态系统和硬件控制能力等其他维度。
- Go、Java、C#: GC 的存在主要被分析为潜在性能损失来源,而这些语言的开发生产力和生态价值可能被低估。
- Python、JavaScript: 缺少静态类型系统被作为稳定性问题的依据,而快速原型和开发速度则被视为次要因素。
工程评价需要综合考虑多种权衡。选择性突出单一标准,可能限制对各技术在不同问题领域适合程度的判断。
案例研究:Web 后端开发中的普遍化
一种例子是主张把 Rust 广泛应用于 Web 后端开发。
对于 API 网关、实时通信服务器等要求高吞吐和低延迟的特定 Web 服务领域,Rust 可以成为一个合理选项。内存安全也可能提高服务器稳定性。
然而,把这些领域的要求扩展到其他 Web 后端领域,就是一种普遍化。在许多普通 Web 应用,如 SaaS、内部管理系统和电商平台中,除性能外还需考虑以下商业与工程因素。
- 开发速度与上市时间
- 生态成熟度,包括认证、支付、ORM 等库的完整度
- 新人学习难度及开发者人才池规模
按照这些尺度,拥有成熟生态的 Go、C#/.NET、Java/Spring、Python/Django 等可能是合适选择。不考虑问题特征和商业约束就广泛主张某种技术的应用范围,可以被看作没有充分进行工程权衡分析。
5.3 异步编程模型的复杂性与工程权衡
Rust 的异步模型 async/await 基于零成本抽象原则设计,目标是在没有垃圾回收器或绿色线程的情况下实现运行时性能。这些设计目标来自利用操作系统线程的系统编程领域。
但这一选择也让开发者承担概念复杂性、生态碎片化与互操作限制等成本。
技术复杂性的来源
Rust 的 async/await 由编译器把异步代码转换为状态机。过程中可能生成包含对自身内存位置引用的自引用结构,因此 Rust 引入 Pin<T> 指针类型,以保证这类结构的地址稳定性。
Pin<T> 及相关的生成器等,是其他主流语言中少见的抽象概念,需要学习其运行原理。这种复杂性可以被视为一种泄漏抽象。Rust 异步生态的开发者也在博客和演讲中讨论这一学习曲线,并提出改进可用性的必要性。23
运行时碎片化与依赖耦合
Rust 有意不在标准库中包含特定异步执行器。这一决定是为了覆盖资源受限的 no_std 环境而保留灵活性,但在实际生态中产生了运行时碎片化这一结构性挑战。
在缺少标准运行时的情况下,tokio 已成为生态事实标准。reqwest、sqlx 等网络与数据库客户端库因而与特定运行时实现强耦合。为了使用某个外部库,开发者可能必须让整个项目采用 Tokio,从而失去与 async-std、smol 等其他运行时的兼容性。在语言层级完整标准仍然缺失时,把生态基础设施集中到一个第三方库,会伴随长期结构性风险。
外部互操作的限制:异步 FFI
这种隔离也明显表现于与其他语言的互操作。Rust 的重要优势之一,是通过 C ABI 顺畅支持 FFI,但这主要限于同步代码。
Rust 的 Future 类型与状态机模型无法直接映射到系统标准 C ABI。因此,把 Rust 编写的高性能异步模块与 C、Python、Go 等语言的事件循环(例如基于 epoll 或 kqueue)集成,需要较高工程成本。开发者必须同步阻塞运行时,或手工编写复杂的回调包装器,才能与其他语言通信。这是面对遗留系统集成和多语言架构等工业需求时,Rust 异步生态的根本障碍。
对开发体验的实际影响
async 模型的内部复杂性在开发与维护中造成以下困难。
- 调试难度增加: async 代码出错时的堆栈跟踪常由运行时内部函数和编译器生成的状态机调用组成,很难追踪根本原因。与同步函数不同,异步函数的局部变量被捕获到状态机对象内部,使用调试器检查状态更困难。
- 成本转移: Rust 异步模型以最小化运行时 CPU 和内存使用(机器时间)为目标,却把成本转移到解决运行时碎片化、与其他语言集成和调试困难(开发者时间)上。
与替代模型的比较
与 Go goroutine 等替代并发模型比较时,这一权衡更加清晰。goroutine 是由语言运行时管理的轻量绿色线程,为开发者提供简化的并发编程模型。
| 维度 | Rust async/await |
Go goroutine |
|---|---|---|
| 设计目标 | 零运行时开销 | 开发生产力与简洁性 |
| 运行时成本 | 最小化 | 存在调度器与 GC 成本 |
| 生态整合性 | 低(依赖 Tokio 等第三方并存在碎片化) | 高(语言内建标准) |
| 学习曲线 | 高(需要 Pin 等概念) |
低(go 关键字) |
| 调试 | 困难(复杂堆栈跟踪) | 较容易(堆栈更清晰) |
在 CPU 密集型工作中,Rust 模型可能具有性能优势。但在网络延迟或数据库响应成为瓶颈的一般 I/O 密集型环境中,Rust 所需的生态碎片化与调试复杂性成本,可能超过 Go 所承担的运行时开销。
Rust 社区部分讨论有时因为 Go 模型并非“零成本”而低估它。但这只用运行时性能单一尺度评价技术,可能忽略互操作、开发生产力和可维护性等其他工程价值。
5.4 重新考虑显式错误处理模型 Result<T, E> 的实用性
Rust 采用基于 Result<T, E> 枚举、模式匹配和 ? 运算符的显式错误处理模型,在编译时强制关注错误。该模型有助于防止遗漏错误处理。为分析其实用性,本节比较替代错误模型,考察概念历史来源,并分析实际使用成本。
1. 与替代模型比较:try-catch 异常处理
讨论 Rust 的 Result 模型时,基于 try-catch 的异常处理常因控制流不可预测而受到批评。不过,异常处理机制具有以下工程特征。
- 关注点分离: 正常逻辑可写在
try块中,异常处理则分离到catch块。控制流会从错误点直接传递到处理点,避免经过多层函数手工传播return Err(...)。 - 编译时检查: “不知道会发生什么异常”的批评并非适用于所有情况。例如,Java 的受检异常要求函数在签名中声明可能抛出的异常,并由编译器强制调用者处理。这以不同于
Result的方式实现了防止遗漏错误处理的目标。 - 系统恢复力: 异常处理系统通过错误日志、
finally资源清理和恢复逻辑,帮助避免程序异常终止并维持服务运行。
2. 概念的历史来源:函数式编程
通过 Result 和 Option 显式处理错误与状态并非 Rust 独有,而是采用了源于函数式编程的既有概念。
数十年来,Haskell 的 Maybe a、Either a b,以及 OCaml、F# 等 ML 系语言的和类型,已经通过类型系统表达值缺失或错误状态,并要求编译器确保所有情况都被处理。
因此,Rust 的贡献可以被理解为并非发明这一概念,而是根据系统编程语境重新解释,并通过 ? 运算符等语法便利将其普及。
3. 实际成本:错误类型转换的冗长
? 运算符适合传播同一错误类型,但实际应用往往使用多个外部库,它们返回各自不同的错误类型,如 std::io::Error 和 sqlx::Error。开发者必须反复编写样板代码,把这些类型转换为统一的应用错误类型。
// 把多种不同错误转换为统一应用错误类型
fn load_config_and_user(id: Uuid) -> Result<Config, MyAppError> {
let file_content = fs::read_to_string("config.toml")
.map_err(MyAppError::Io)?; // std::io::Error -> MyAppError
let config: Config = toml::from_str(&file_content)
.map_err(MyAppError::Toml)?; // toml::de::Error -> MyAppError
// ...
Ok(config)
}
为减少这类重复转换,生态中通常使用 anyhow、thiserror 等外部库。为实现灵活错误处理而把第三方库视为事实标准,说明实用应用开发还需要超出语言基本功能的能力。
4. 案例研究:Cloudflare 故障与 unwrap() 的使用
Rust 错误处理模型在实际生产环境中的表现,可以通过 2025 年 11 月的 Cloudflare 服务中断事故考察。24 事故涉及一个返回 Result 的函数,其错误情况没有通过 match 或 ? 处理,而是调用 unwrap() 导致 panic。
Rust 通过 Result 要求开发者明确考虑错误,但同时也提供 unwrap() 绕过这一要求。理论上 unwrap() 主要用于原型和测试,不过实际开发中,也可能为了减少复杂错误处理逻辑的实现成本而用于生产代码。
该案例说明,语言强制不能完全排除优先考虑开发便利的选择。即使编译器强制规则,开发者若选择 unwrap() 这样的逃生路径,便利性的结果也可能是系统中断。它展示了 Rust 的强制安全模型与实际工程中的人为因素结合时可能出现的限制。
5.5 Rust 生态的质量成熟挑战与社区话语分析
Cargo 和 Crates.io 推动了 Rust 的快速采用与增长,带来共享库(crate)数量的扩张。但在量的增长背后,仍存在如何在生产环境确保稳定性与可信性的质量成熟问题。本节分析生态面临的主要质量挑战,以及社区回应这些问题时的特征性话语结构。
1. crate 生态质量成熟度的主要挑战
在生产环境使用 Rust 的开发者,可能面对以下实际库生态问题。
- API 稳定性不足: 很多 crate 长期停留在语义化版本 1.0.0 以下的
0.x版本。这表示公开 API 尚未稳定,可能出现不保证向后兼容的破坏性变更。对于有生产依赖的项目,这会增加潜在维护成本和风险。 - 文档质量差异: 尽管
cargo doc能生成标准化 API 文档,实际 crate 的文档水平差异很大。一些 crate 除 API 列表外缺少具体使用示例或设计哲学说明,导致开发者为使用库不得不亲自阅读源代码。这会削弱库本应带来的生产力提升。 - 维护持续性问题: 与许多开源生态相同,即使是关键 crate 也可能仅由少数志愿者维护。若主要维护者因个人原因停止项目,对安全漏洞或重大缺陷的后续处理可能长期延迟,并影响所有依赖该 crate 的生态稳定性。
2. 对生态问题的批评与观察到的回应模式
当生态质量问题受到批评时,在网络论坛等公开讨论空间中,有时会出现把讨论引向不同于技术问题本质的特定话语模式。
- 通过“鼓励参与”转移责任: “Pull requests are welcome”或“需要就自己贡献”等回应,鼓励开源的重要价值——自愿参与。但当它们被用来回答对库缺陷或文档不足的批评时,也可能发挥把解决责任转给最初报告者的修辞功能。并非所有用户都有修改库的专业能力或时间,因此这种反应可能压制反馈循环。
- 成功案例的代表性与统计视角: 面对对整个生态成熟度的批评,有时会以
tokio、serde等少数维护良好的核心 crate 作为反驳。这些案例确实展示了 Rust 生态的潜力和可达到的质量水平。但这种论证需要从样本代表性角度审查。少数成功案例不一定能代表数千个库的平均成熟度,或普通开发者实际面对的情况。这并非只是指出逻辑谬误,而是一个工程与统计问题:所选样本是否足以描述总体?把讨论限制在少数顶级案例,可能掩盖单个库的现实问题,并高估生态当前状态。
5.6 开发工具链的技术挑战与生产力
Rust 开发者体验在提供特定能力的同时,也伴随若干可能影响大型项目生产力的技术挑战。本节分析编译器资源使用、IDE 集成和调试环境,以及构建系统灵活性。
5.6.1 编译器资源使用及其影响
Rust 编译器 rustc 在编译过程中往往需要较多时间和内存。这部分来自语言设计,包括实现零成本抽象所使用的单态化策略,以及对 LLVM 后端的依赖。
- 编译时间: 单态化为每个泛型类型生成代码,增加编译器必须处理和优化的代码量。这会延迟“修改代码→编译→测试”的开发反馈循环,尤其在项目规模增大时降低生产力。
cargo check可提供较快检查,但完整构建与测试仍可能耗时。 - 内存使用: 编译器内存占用会给个人笔记本或低配置 CI/CD 工作节点等资源受限环境带来问题。大型项目中,编译器进程可能超过系统可用内存,被操作系统 OOM killer 强制终止,从而降低开发体验的稳定性。
这些成本并非固定不变。Rust 项目与社区把编译速度视为改进课题。用于加快调试构建的 Cranelift 后端,以及增强 rustc 并行处理能力的工作,都表明这一权衡正在被积极管理。
5.6.2 IDE 集成与调试环境:抽象背后的成本
IDE 集成和调试环境展示了 Rust 的设计哲学如何在开发者日常工作中产生成本。Rust 拥有语言服务器并支持标准调试器,但其抽象复杂性可能造成认知负担和生产力下降。
语言服务器 rust-analyzer 的现实与限制
rust-analyzer 实时分析 Rust 复杂的类型系统和宏功能,提供代码补全、类型推断与诊断,被广泛视为提高生产力的重要工具。
这种深度分析本身也是成本。rust-analyzer 会把项目代码及依赖常驻内存,并在开发者修改代码后重新计算复杂的 trait 解析和宏展开,可能产生以下问题。
- 资源使用: 大型项目中,
rust-analyzer进程本身可能占用数 GB 内存,对资源受限的开发环境形成负担。 - 分析不稳定: 在使用复杂泛型类型或过程宏的代码中,类型推断可能失败或给出不准确诊断,使开发者不能完全信任语言服务器,而必须依赖编译器最终诊断。
这不一定只是 rust-analyzer 自身的问题,也体现了实时执行类似编译器工作的语言服务器局限,以及 Rust 语言的复杂性。
抽象与调试的权衡
Rust 的零成本抽象原则可能在调试过程中把成本转给开发者。虽然可用 LLDB 或 GDB,但调试 Rust 抽象类型的体验与其他语言的集成环境不同。
例如,在 Java 或 C# IDE 中检查 Vec<String> 类似的集合时,可能直接显示 ["hello", "world"]。Rust 调试器则可能暴露 Vec 的字段:指向堆内存的指针、容量以及当前长度。
开发者必须解释低层内存表示才能理解程序的逻辑状态。抽象所消除的运行时成本,以调试便利性下降和额外认知负担的形式出现。
调试异步代码
这一问题在调试 async/await 时尤其明显。如第 5.3 节所述,编译器把 async 函数转换为状态机,使传统基于调用栈的调试变得困难。
即使在错误点停止并检查调用栈,也可能看不到开发者编写的 function_a 调用 function_b 的逻辑路径。看到的反而可能是 Tokio 等异步运行时的调度器内部函数,以及编译器生成、需要开发者自行解释的状态机 poll 调用。因此,“代码如何到达这里?”可能很难回答。
这与 C# 的 Visual Studio 或 Java 的 IntelliJ IDEA 能重建并显示异步代码逻辑调用栈形成对照。Rust 的异步调试环境说明,最小化运行时开销的设计哲学可能在开发与维护阶段产生复杂性成本。
5.6.3 Cargo 构建系统的灵活性
Rust 官方构建系统 Cargo 通过标准化项目管理、依赖解析以及“约定优于配置”哲学提高生产力。这些是 Cargo 的重要优点。
但当项目需求超出标准范围时,相同特征也可能表现为僵硬。复杂代码生成或与外部库特殊集成等非标准构建流程,仅靠 build.rs 往往难以灵活处理。在大型 monorepo 中,feature flag 组合也可能变得复杂,使依赖管理本身成为额外维护成本。这会限制需要支持多样构建场景的大型工业环境。
这些因素表明,Rust 开发者体验在提供实际优势的同时也伴随技术挑战。与其孤立评价开发环境,不如理解它们是设计选择的结果。下一节将摆脱把各生态归结为单一哲学的视角,同时比较分离式工具链与集成式体验,并纳入生态成熟度。
5.7 开发环境比较:成熟度与设计哲学的交叉点
上一节分析了 Rust 开发环境的技术挑战。此类分析容易滑向“Java/C# 的集成 IDE”与“Rust 的 VS Code 环境”的二分比较,却忽略两个生态都同时提供分离式工具链和集成式体验。
因此,比较应把两种哲学并列,并把生态成熟度作为另一个变量共同考量。
1. 第一种比较:VS Code 等分离式工具链环境
语言服务器协议使多种语言能够在 Visual Studio Code 等编辑器中获得类似支持。在相同条件下,各生态情况如下。
- Java/C#: Eclipse JDT LS、Red Hat Java 扩展和 C# 的 Roslyn LSP,经过多年开发与企业支持,已经获得稳定性和成熟度,并为企业项目提供补全、诊断和重构。
- Rust:
rust-analyzer对生态增长贡献很大。但如第 5.6 节所述,宏和 trait 解析等语言复杂性仍带来稳定性与资源消耗挑战。 - 分析: 在同样的分离式工具链条件下,Java/C# 语言服务器建立在更长历史与相对稳定的语言规范上,因而表现出成熟度。
rust-analyzer仍需解决语言特有挑战。这并不证明任何一方优越,而是说明历史路径与技术问题不同。
2. 第二种比较:专业 IDE 的集成环境
两个生态在基本 LSP 功能之外也都提供集成环境。
- Java/C#: IntelliJ IDEA 和 Visual Studio 凭借积累的经验,在代码分析之外提供项目智能。基于语义结构的重构、调试和性能分析,使它们成为开发平台,也体现了集成哲学的成熟度。
- Rust: JetBrains RustRover 与 CLion 表明 Rust 生态也存在集成式选择。除了
rust-analyzer,这些 IDE 尝试通过自有分析引擎提供调试器集成和重构,是 Rust 开发者体验的进步。 - 分析: 此领域仍存在成熟度差距。与 IntelliJ 的 Java 支持相比,RustRover 尚处于较早阶段。在短期内重现 Java 生态数十年积累的重构与调试功能并不容易。这更适合被理解为成长中技术经历的阶段,而非 Rust 固有的技术限制。
3. 结论:重构比较框架
直接比较“Java/C# 的集成 IDE”和“Rust 的 VS Code”,会把一个生态中成熟的部分与另一个生态中流行的部分交叉比较,形成不对称框架。
比较可得出以下内容。
- 两个生态都提供遵循两种哲学的开发环境。
- 无论分离式工具链还是集成式体验,Java/C# 生态都通过时间和投入表现出较高成熟度。
- Rust 开发环境正在发展,但因语言复杂性和较短生态历史而面临成熟度挑战。
因此,难以把开发环境差异归结为某一方的依赖性或某种哲学的优越性。核心差异在于各自到达的成熟阶段不同。Java/C# 通过时间和投资,在集成与分离两种方式上都达到较高完成度;Rust 则处于一边解决语言复杂性、一边成长的过程中。工程评价应从承认这一现实出发,选择适合项目要求的工具与哲学。
6. “零成本抽象”的实际成本分析
第 6 章分析 Rust “零成本抽象(ZCA)”原则所伴随的实际成本。
第 6.1 节考察运行时成本如何通过单态化机制转移为更长编译时间和更大二进制。第 6.2 节进一步聚焦二进制大小,分析 ABI 不稳定性、静态链接与具体案例,并探讨它们对适用领域的影响。
6.1 成本转移机制:单态化的作用
Rust 的设计原则之一是零成本抽象。其含义是,开发者使用泛型、迭代器等抽象功能时,不应因此降低程序运行时性能。
这一原则与 C++ 设计哲学相连。Bjarne Stroustrup 提出的“你不为未使用的功能付费”表达了同一核心思想。C++ 通过模板等机制在编译时生成代码,以消除运行时开销。
Rust 继承这一哲学,并与所有权和借用检查器结合以提供内存安全。但“零成本”仅指零运行时成本,并不表示不存在任何成本。Rust 的 ZCA 可以理解为一种成本转移机制:以运行时性能为收益,把成本转移到开发周期的其他阶段。
这种转移与单态化编译策略有关。编译 Vec<T> 等泛型代码时,编译器会为实际使用的每个具体类型,如 Vec<i32>、Vec<String>,生成专门代码。该策略以消除运行时类型检查和虚调用等间接成本为目标,但会产生两类其他成本。
- 编译时间增加: 编译器为每个泛型实例复制代码并分别优化,增加编译器、特别是 LLVM 后端的工作量,从而延长编译时间。
- 二进制增大: 专门化代码副本都包含在最终可执行文件中。同一逻辑存在多个版本,尤其与静态链接结合时,会增大二进制。
作为替代,Rust 通过 &dyn Trait 等 trait 对象提供动态分派。它不复制代码,而生成单一实现并在运行时选择所需行为,以接受运行时开销换取更短编译时间和更小二进制。
因此,Rust 的零成本抽象是以运行时性能为中心的设计哲学。但由此产生的编译时间和二进制大小增长会影响生产力与部署,评价 ZCA 时必须考虑。这一设计为追求零运行时开销,支付编译时间和二进制大小的成本。
6.2 二进制大小:设计原则对适用领域的影响
Rust 可执行文件往往比提供相似功能的 C/C++ 程序更大。这在资源受限的系统编程领域尤为重要,而该领域正是 Rust 被讨论为 C/C++ 替代方案的场景之一。本节分析技术原因,并通过具体比较考察实际影响。
1. 技术原因:ABI 不稳定与静态链接
Rust 二进制增大的原因之一,是标准库 libstd 不维持稳定 ABI 的设计选择。C 数十年来依赖稳定的 libc ABI 支持动态链接,使多个程序能够共享系统安装的库。因此,动态链接的 C 可执行文件只需包含自身代码,可以保持较小体积。
Rust 没有稳定 libstd 的内部 ABI,以便语言和库实现快速演进。这是优先快速演化而非稳定二进制兼容的选择。由于难以保证跨版本动态链接,Rust 默认采用静态链接,把所需库代码嵌入每个可执行文件。即使小程序也会包含相关 libstd 部分,从而增大体积。
2. 案例研究:CLI 工具与核心实用程序
实际程序大小比较可以说明这一影响。
案例 1:grep 与 ripgrep
ripgrep 是 Rust 编写的文本搜索工具,常与 C 编写的 grep 比较。在普通 Linux 系统上,动态链接 grep 可能只有数十 KB,而静态链接 ripgrep 可达数 MB。对单个应用部署而言,这简化依赖管理;但若替换操作系统整套基础工具,则可能增加总存储量。
案例 2:BusyBox 与 uutils
资源受限的嵌入式 Linux 常用 BusyBox,在单一二进制中提供 ls、cat 等命令。C 编写的实现不到 1 MB。出于类似目的开发的 Rust 项目 uutils 则达到数 MB。具体数值会随版本和构建环境变化,但这一趋势是标准库设计与默认构建方式差异带来的结构性结果。下表使用 Alpine Linux 软件包数据。
表 6.2:核心实用程序实现的软件包大小比较(Alpine Linux v3.22)25
| 软件包 | 语言 | 结构 | 安装大小(约) |
|---|---|---|---|
busybox 1.37.0-r18 |
C | 单一二进制 | 798.2 KiB |
coreutils 9.7-r1 |
C | 独立二进制 | 1.0 MiB |
uutils 0.1.0-r0 |
Rust | 单一二进制 | 6.3 MiB |
这些数据表明,Rust 默认构建方式与 BusyBox 所针对的嵌入式环境需求存在差异。
3. 缩小体积的技术及其权衡
Rust 有多种缩小二进制的技术,并通过 min-sized-rust 等指南传播。
- 改变 panic 处理(
panic = 'abort'): panic 后不展开栈,而是立即终止程序,从而移除相关代码与元数据。这能减小体积,但会跳过资源清理,并使catch_unwind无法恢复 panic。因此,它构成二进制大小优化与系统恢复力之间的工程权衡。 - 排除标准库(
no_std): 不使用提供堆分配、线程、文件 I/O 等操作系统依赖功能的libstd。这能减小体积,但必须自行实现Vec<T>、String等结构和功能,或依赖外部 crate。
因此,要获得接近 C/C++ 的二进制大小,可能需要关闭语言默认提供的功能及部分安全机制。这说明 Rust 默认设计哲学更重视功能和运行时性能,而非二进制大小。
零成本抽象及其单态化实现造成的编译时间与二进制增长,体现了 Rust 的设计哲学。
这些成本不是“成熟度问题”,而是为了获得运行时性能,以开发时间和部署大小交换的内在权衡。它展示了“成本不会消失,只会转移到其他地方”的工程原则。开发者应理解“零成本”背后的成本转移机制,并评价编译速度、二进制大小等项目约束是否与 Rust 设计相符。
7. 工业应用的约束条件
第 7 章分析 Rust 在实际工业领域应用时面对的约束。
讨论从嵌入式和内核环境(7.1)与任务关键系统(7.2)等特殊领域的挑战开始,随后考察一般工业采用的壁垒(7.3),最后对大型企业采用叙事进行多维分析(7.4)。
7.1 嵌入式与内核环境:应用现实与工程挑战
嵌入式系统和操作系统内核是 Rust 被评价为 C/C++ 替代方案的领域。但在这两个领域应用 Rust 仍存在若干工程挑战。正如内核中的 C 无法使用 glibc 等用户空间库,内核中的 Rust 也无法使用依赖操作系统功能的标准库 libstd。
因此,挑战不在于 no_std 本身的存在,而在于熟悉 std 的 Rust 开发者转向 no_std 时所面对的开发模型差异与成本。C 开发模型通常预设低层环境;而习惯 std 生态的 Rust 开发者,则必须承担堆分配、线程、Vec<T>、String 等标准结构以及依赖 std 的 crate 不可用所带来的认知成本和生态收缩。
Rust for Linux 是解决这些问题的一项尝试,其方式可概括如下。
- 构建安全抽象层: 项目目标之一,是用 Rust 所有权与生命周期规则包装现有 Linux 内核中由 C 编写的不安全低层 API。
kmalloc、kfree等内核内存分配函数、锁机制和引用计数,可以被抽象为类似 RustBox<T>、Mutex<T>、Arc<T>的安全结构。开发者由此无需直接操作所有内核细节,而可利用编译时安全检查并聚焦高层逻辑。 - 使用
unsafe: 但在抽象层底部,调用 C 函数或直接访问硬件寄存器仍需要 unsafe 代码。这是与 C 生态进行 FFI 的结果。策略是在特定边界隔离 unsafe 操作,并允许其上层编写安全 Rust。 - 实际应用与文化挑战: 在这些基础上,Rust 已在 Android Binder IPC 驱动、Apple M1/M2 GPU 驱动等真实系统部分中实验采用。除技术障碍外,部分 C 内核开发者的怀疑态度,以及 Linux Kernel Mailing List 上的文化和哲学争论,也构成集成过程的一部分。
为定量分析 Linux 内核集成情况,本书使用 cloc v2.04 分析了 kernel.org 发布的 Linux v6.15.5(截至 2025 年 7 月 9 日)源代码。26 排除注释与空行后,总源代码行数为 28,790,641,其中 Rust 为 14,194 行,约占 0.05%。
该数字描述一个时间点,随着 Rust 内核集成继续推进可能变化。它展示 2025 年中 Rust 在内核 C 代码库中的相对规模与集成状态。代码数量不代表重要性或技术影响力。现有 Rust 代码主要集中于驱动开发基础设施。第 8.4 节稍后将分析基于这类数据的批评如何在技术话语中被接收与防御。
下表汇总该内核版本中代码占比较高的语言。
表 7.1:Linux 内核 v6.15.5 中语言占比(行数与百分比)¹
| 排名 | 语言 | 代码行数 | 占比(%) |
|---|---|---|---|
| 1 | C & C/C++ Header | 26,602,887 | 92.40 |
| 2 | JSON | 518,853 | 1.80 |
| 3 | reStructuredText | 506,910 | 1.76 |
| 4 | YAML | 421,053 | 1.46 |
| 5 | Assembly | 231,400 | 0.80 |
| … | … | … | … |
| 14 | Rust | 14,194 | 0.05 |
¹以总计 28,790,641 行代码为基准,省略部分语言。
除 Rust 在内核中的 0.05% 占比外,技术上还需要分析安全抽象的结构特征。Rust 的目标是用所有权规则封装既有 C API,建立内存安全保证层。但这一层的基础内部仍建立在需要开发者手工验证的 unsafe 块上。
近期报告的 CVE-2025-68260(Rust Binder 竞争条件)提供了具体案例。在 Android Binder 的 Rust 实现中,从共享列表移除元素的 unsafe 操作遗漏同步机制,导致数据竞争和内存破坏。
工程结论是:尽管 Rust 明确以防止数据竞争为设计目标,在需要复杂内核级并发控制的领域,仍会伴随可能发生人为错误的 unsafe 实现。Rust 抽象并非完全消除风险,而是选择把风险限制到特定的 unsafe 代码区域。
7.2 任务关键系统与国际标准缺失
在航空、国防、医疗等高可信任务关键系统中选择语言时,除技术性能外,工业标准与生态成熟度也是主要标准。
这些领域通常要求符合 ISO/IEC 等国际标准,以获得软件的稳定性和可预测性。标准化语言拥有固定规范,支持长期维护,并形成商业生态基础,使多个供应商能够提供兼容编译器、静态分析工具和认证支持。C、C++、Ada 都具备这类标准化流程与供应商生态。
但 Rust 并非国际标准语言,并采用可随发展而改变语言规范的模型。快速演进有助于短期功能改善,却可能与对变化保守、强调长期稳定的任务关键领域要求冲突。因此,法规遵循和认证流程更加复杂,商业供应商支持较难获得,构成进入该领域的结构性壁垒。
7.3 一般工业采用的壁垒与变更策略
Rust 从特定领域向一般工业扩散,面对以下障碍。
- 人才供给与培训成本: Rust 开发者人才池小于 Java、C#、Python。企业招聘可能更困难,人力成本也可能更高。把现有开发者转向 Rust,还需要投入所有权等概念的学习成本,并经历初期生产力下降。
- 企业生态成熟度: 大型企业应用常用的 ORM 框架、云服务 SDK、认证和授权库等领域,部分 Rust 生态不如 Java 或 .NET 成熟。这会成为重视开发速度与稳定性的企业环境的采用障碍。
- 遗留系统的隐含规范: 长期运行的系统在代码、测试、部署脚本和运营流程中积累未记录行为。语言迁移若无法完整恢复,就会发生与内存安全无关的功能与兼容性回归。
- 互操作与过渡成本: 渐进采用需要 FFI、数据所有权转换、错误模型翻译,以及构建和调试工具的双轨运行。全面重写可减少此类边界,但完成前必须并行运行新旧系统,失败时损失也更大。
遗留是系统状态,不是善恶判断
遗留(legacy)通常指组织继承并持续运行的既有系统,可能伴随老旧技术、复杂依赖、支持终止风险或较高变更成本。但该词本身不包含“缺陷很多”或“必须废弃”的判断。相同系统可以既是保存未记录业务规则、用户兼容性、数据和成熟运营流程的资产,也是结构脆弱、维护成本高的负债。
因此,应评价的不是遗留标签,而是以下可测量属性。
- 安全补丁与供应商支持的可持续性
- 故障频率、影响范围与恢复时间
- 变更所需时间与回归风险
- 测试、文档、可观测性与人才供给
- 法规遵循、性能、扩展性和总生命周期成本
软件不会像硬件那样随使用时间产生物理磨损。但运行环境、硬件、外部接口、威胁模型与需求会变化,因此也不意味着软件可以不经修改永久使用。27 与其按任意年限废弃,不如判断它是否满足当前需求,以及是否能够持续维护。
区分沉没成本与未来成本
已经支出的开发费用是无法收回的沉没成本,不能仅凭这一点维持系统。但未来维护费、重写费、数据迁移费、并行运营费、服务中断风险、功能回归和机会成本,都是应纳入决策的真实未来成本。“因为过去花了钱所以保留”与“因为过渡成本和风险高于替代方案收益所以保留”是不同判断。
反过来,仅因系统老旧就替换,也不是充分依据。维护、现代化、部分替换和全面重写,应使用同一标准比较预期收益与未来成本。CMU 软件工程研究所同样把遗留现代化视为一个决策问题,需要在全面替换与渐进集成之间比较风险和条件。28
因此,工业界的变更策略应作为一个连续谱来评价。
| 策略 | 一般适用条件 | 核心收益 | 核心成本或风险 |
|---|---|---|---|
| 加固既有实现 | 缺陷局部、稳定行为资产较大 | 过渡风险最低 | 结构限制可能保留 |
| 同语言现代化 | 渐进改善架构与验证水平 | 易于部署、回滚与回归控制 | 规则遵守依赖组织能力 |
| 选择性采用 Rust | 高风险模块边界明确 | 把投资集中于需要保证的区域 | FFI 与双语言维护成本 |
| 用 Rust 实现新功能 | 很少需要复现既有行为 | 无重写风险地积累 Rust 经验 | 需要与既有系统集成 |
| 全面重写 | 既有结构无法满足根本需求,且具备充分规范、预算和过渡计划 | 同时重新设计语言与架构 | 进度超支、回归和运营过渡失败影响较大 |
这些策略不是等级,而是条件性选项。企业应按照缺陷类型与频率、安全事故成本、变更速度、人员、可容忍停机范围、现有测试与规范质量进行比较。全面重写是一种可能选择,但不能基于“其他改进都无意义”的前提自动成为默认答案。
这些因素独立于语言技术特征,是企业选择技术栈与变更策略时必须考虑的商业与工程约束。
7.4 “大型企业采用”叙事的多维分析:语境、限制与战略含义
支持 Rust 实用性与未来价值的一项论据,是 Google、Microsoft、Amazon 等技术企业的采用案例。这些企业使用 Rust 的事实,常被用作其技术特征和解决特定问题能力的指标。
但工程评价除了“哪家企业使用”,还必须分析采用的具体语境、规模和条件。这种多维分析有助于区分“大型企业采用”叙事、技术现实及其战略含义。
1. 采用语境、规模与条件
首先是语境。这些企业并未把所有系统和产品全面替换为 Rust,而是在其特征特别相关的领域选择性采用,例如操作系统低层组件、浏览器中对安全敏感的渲染引擎部分,以及无法接受 GC 延迟的高性能基础设施。考虑到这些企业仍在更广范围主要使用 C#、Java、Go、C++,Rust 是战略工具,而非全面替代。
其次是规模。“采用”一词容易暗示组织整体接受,但现实可能不同。相对于企业全部软件项目与开发者规模,Rust 仍处于增长阶段。少数团队的采用可能借助企业标志被扩大解释为组织全体的标准技术,形成光环效应。
第三是条件。大型技术企业拥有承受新技术采用成本的资源,包括学习曲线带来的培训费、弥补生态不足所需的内部工具与库开发,以及容忍初期生产力下降的时间和财务余裕。把这些案例作为人员和预算有限企业也能复制的普遍证据,可能忽略样本代表性。特定样本群体——大型技术企业——的结果,不能自动假定会在整个产业总体中复现。这与第 5.5 节的样本代表性问题相连。
2. 战略采用的含义
这些企业战略性选择 Rust,与它们试图解决的具体问题相关。Android、Windows 内核、Chrome 等系统建立在数亿行既有 C++ 代码上。如何在不牺牲性能的情况下引入内存安全,一直是重要挑战。
在这一背景下,Rust 被选择为在保持既有 C++ 性能与控制水平的同时,以可扩展方式渐进引入内存安全的技术方案。这说明 Rust 能够解决工程组织面对的真实问题。
这种选择不仅可以被看作解决利基问题,也可被解释为系统编程范式变化的先行指标。
3. 结论:多维分析
大型企业采用 Rust 具有两面性。一方面,它不应被当作所有问题场景的证据,而应分析具体语境和限制。另一方面,选择性采用展示了 Rust 解决特定系统编程问题的适用性,也可以被解释为范式变化信号。
工程判断可以从多维分析出发,同时评价技术的限制与潜力。
本章分析的工业采用约束,是成熟度问题与内在权衡共同作用的结果。
因缺乏国际标准或 ABI 稳定性问题而形成的关键任务系统准入障碍,可以视为 Rust 优先“快速演进”的开发模式所带来的内在权衡。
相较之下,开发者人才池不足或某些企业领域的库生态不完善,则属于随着技术采用扩大和社区成长而可能缓解的成熟度问题。
总之,Rust 若要超越现有适用领域并扩展到更广泛的产业,就需要处理这两类障碍。在生态成熟的同时,也需要审视语言的设计哲学如何与不同产业的要求相适应。
第四部分:技术共同体话语分析
前三部分分析了 Rust 的技术特性与工程权衡;第四部分将分析围绕 Rust 形成的社会现象,即“话语”的结构。
本部分将特定技术共同体中防御性话语的形成过程及其逻辑模式作为一项案例研究。分析对象明确限定为部分网络讨论空间中可观察到的特定倾向,而非 Rust 项目的官方立场;本书并不试图把少数声音过度解释为整个社区的意见。本书之所以关注这种非官方话语,是因为即使它只代表少数,也可能塑造新开发者对技术的第一印象,并影响其进入生态的体验。进一步说,这些公开话语还可能成为大型语言模型(LLM)的训练数据,使既有偏见被技术性地重新学习和放大。本部分通过 Rust 这一具体案例理解技术话语的形成。第 8 章分析“银弹叙事”29如何形成,并在遭遇批评时如何作为集体防御机制运作;第 9 章考察这种话语对开发者技术选择和生态可持续性的影响;最后,第 10 章综合前述分析,提出 Rust 生态的挑战与前景并作结。
第四部分超越对特定技术的支持或批评,旨在理解技术生态如何运作。
8. “银弹叙事”与集体防御机制的形成
第 8 章分析“银弹叙事”如何形成,以及它在遭遇批评时如何作为“集体防御机制”发挥作用。
讨论首先考察该叙事的形成过程和效果(8.1),随后检视“完全替代”叙事的局限(8.2)与技术话语的历史先例(8.3)。接着分析回应批评的具体论证模式(8.4)、守门行为(8.5)、治理争议(8.6),以及政府建议和产业成功案例的适当引用范围(8.7)。最后考察这些话语背后的官方改进努力与治理(8.8),并结束本章。
8.1 “银弹叙事”的形成过程及其效果
本章对“银弹叙事”的分析限定其对象。分析不涉及 Rust 基金会或核心开发团队的官方立场,也不试图把整个 Rust 社区概括为单一群体。本章关注的是一种与 Rust 项目官方自我批评文化呈现不同倾向的特定话语。
事实上,Rust 的核心开发者和基金会把本书前几章所述的 async 复杂性、编译时间、工具链问题等视为需要改进的事项。他们通过 RFC(Request for Comments)流程和官方博客明确技术限制,并与社区共同寻找解决方案。
因此,本章的分析对象与这些官方改进活动相区分,仅限于部分网络技术论坛和社交媒体中可观察到的、某些支持者的防御性或泛化性修辞。30 由于难以测量这种非官方话语在数量上的占比,本文重在分析其“逻辑结构”和“效果”,而非其“频率”。
如 2.3 节所分析,影响 Rust 成长的因素之一,是围绕“无性能损失的安全性”等价值形成的叙事。这种叙事塑造了共同体身份,促进志愿者贡献,并推动生态成长。
然而,当它面对外部批评或技术限制时,有时会被简化为“Rust 能解决所有系统编程问题”的银弹叙事,并进一步形成集体防御机制。为了分析这种现象的社会动因,可以把社会心理学中的若干概念作为分析框架。这不是要“诊断”某个群体或个人的心理,而是说明具有共同身份的技术共同体中,话语形成的结构和效果。
例如,认知失调(cognitive dissonance)理论描述个人面对与自身投入或信念相冲突的信息时所产生的状态。把这一框架用于本案,可以设想开发者为克服 Rust 的学习曲线投入了大量时间和精力。此后面对语言缺点或限制的批评,可能与为自身投入进行正当化的动机相冲突。为了缓解这种失调,个人可能倾向于强调所选技术的优点,并缩小其缺点。
进一步从社会认同理论(social identity theory)看,当技术熟练度与开发者的职业身份相连时,社区可能形成内群体(in-group)。此时,外部批评可能不再被视为技术审查,而被理解为对内群体价值或身份的挑战。这种动力可能促成防御性话语,相对贬低作为外群体(out-group)的其他技术生态。
这种内外群体结构还可能在某些网络空间中通过回音室效应(echo chamber effect)得到强化。回音室指相似观点在封闭系统内经重复而被放大的现象。符合社区主导叙事的信息得到传播,批评意见或替代观点则可能被边缘化。参与者原有信念因而进一步强化,巩固银弹叙事并维持针对外部批评的防御姿态。
在这种心理基础上,银弹叙事似乎又通过特定的信息框架得到强化。
选择性框架的结构性原因
Rust 话语选择性强调与 C/C++ 的对立,而较少重视 Ada/SPARK 等替代方案,难以仅用“争夺话语主导权”的意图解释。开发者生态的运作方式中存在多种结构性原因并共同作用。
- 信息可获得性与学习资源的不对称: 软件开发者学习和比较技术的过程依赖可获得信息的数量与质量。C/C++ 积累了数十年的书籍、大学课程、在线教程和社区讨论材料。Rust 也通过官方文档《The Book》和社区建立了学习生态。相比之下,Ada/SPARK 主要围绕航空、国防等高可靠性产业发展,因此普通开发者可获取的最新学习资料和公开社区讨论相对较少。这种信息可获得性的差异,使开发者更容易把 C/C++ 视为主要比较对象。
- 产业关联性与市场需求变化: 技术话语倾向于围绕当前市场中实际使用和竞争的技术形成。C/C++ 是操作系统、游戏引擎、金融系统等众多产业的基础技术,而 Rust 在云原生、Web 基础设施、区块链等高性能系统领域作为 C/C++ 的替代方案兴起。两者在产业现场确实存在竞争或替代关系。相比之下,Ada/SPARK 主要服务的关键任务系统市场,其要求和生态与普通软件市场不同,直接比较的必要性相对较低。
- 教育课程与开发者的共同经验: 在计算机科学教育中,C/C++ 常被用作操作系统、编译器和计算机体系结构等课程的实践语言,因而像程序员之间的“通用语”。C/C++ 的内存管理问题是许多开发者经历过的共同经验和问题意识。Rust 话语指出 C/C++ 问题时能够获得共鸣,正是因为存在这种共同背景。相比之下,Ada 在多数标准课程中并不涉及,作为比较对象较难形成开发者的共同认知。
综合这些结构因素,C/C++ 中心的对立结构与其说是某个群体的有意排除,不如说是信息生态不对称、市场现实需求和开发者共同教育背景共同作用的结果。
对“内存安全”议题的占位与话语主导权
这一叙事形成过程的结果之一,是在系统编程领域抢占了“内存安全”议题。
Java、C#、Go 等主流语言早已通过 GC 等机制默认提供内存安全。但在这些生态中,内存安全是一项前提,因此通常不是讨论对象。
部分支持 Rust 的话语在与 C/C++ 的对立框架中,把内存安全强调为语言的差异化特征和价值。结果是,开发者通过 Rust 认识“内存安全”这一术语,产生了议程设置(agenda-setting)效果。这可以被分析为:把某项价值带到话语中心,塑造公众对该概念的认识,并把它转化为品牌资产。
总之,银弹叙事在部分支持者中通过选择性设置比较对象和占据议题的方式形成。它有助于宣传 Rust、强化共同体身份,同时也留下了可能阻碍对技术生态形成更广阔视角的批评空间。
对信息生态及 AI 训练数据的扩散效应
当围绕某项技术形成主导话语(dominant discourse)时,它可能越过社区边界,扩散到整个技术信息生态并产生影响。
第一,它会影响新学习者的信息可获得性。当人们搜索某一领域(如安全系统编程)的信息时,在线数量占优的话语更可能出现在搜索结果前列。学习者因而可能首先把 Rust 当作 C/C++ 的替代方案,却没有意识到 Ada/SPARK 等较少被讨论的技术选择。这会限制技术选择的机会集合。
第二,它可能造成大型语言模型(LLM)的训练数据偏差。LLM 从互联网文本中学习,因此训练数据的数量分布会影响模型的回答倾向。如果强调 Rust 优点的框架主导话语,模型回答“最安全的系统编程语言是什么?”时,可能按照训练数据中的出现频率优先提及 Rust,或给予它比 Ada/SPARK 更大的篇幅。既有话语偏见便可能被人工智能重新学习并放大。
8.2 “完全替代”与“只有重写才算改进”叙事的局限
银弹叙事常扩展为“Rust 将替代既有系统编程语言”的预测。更强的形式则表现为:“C/C++ 代码唯一有意义的重构是用 Rust 重写,其他改进都没有意义。”这已经超出说明技术优点的层面,把有效变更策略限制为唯一一种。
1. 范畴混淆:把代码改进等同于语言替换
如 3.3 节所区分,重构是在保持既有行为的同时改进内部结构,重写则是以新实现替换旧实现。前者可以改善现有代码的耦合、所有权边界、可测试性、错误处理和变更影响范围;后者可以重新设计这些结构并引入 Safe Rust 的编译期保证,但必须重新实现既有行为。
若把两项工作都称为“重构”,以下两个问题就会被合并:
- 如何让现有实现更易理解、更可验证?
- 是否应以另一种语言的新实现替代现有实现?
规定第二个问题是第一个问题的唯一答案,是改变问题范畴并预设结论。
2. 虚假二分与完美主义谬误
“C/C++ 重构无法保证完全内存安全”可能为真,但不能由此推出“因此毫无意义”。提高可测试性、缩小裸指针范围、隔离危险代码、应用静态分析和简化接口,即使不是完整保证,也能降低缺陷发生概率与影响范围。
反过来,Rust 重写也不会消除所有错误和事故。逻辑错误、死锁、资源耗尽、因 panic 导致的可用性下降、unsafe 与 FFI 边界、功能回归仍需分别处理。若对一种策略要求完整性,却只要求另一种策略提供特定保证,比较标准就不对称。
3. 变更方式与保证方式是不同维度
为了使技术讨论清晰,必须分离以下两个维度:
- 变更方式:维护、重构、现代化、部分替换、新实现、全面重写
- 保证方式:开发者纪律、编码标准、静态与动态分析、运行时检查、编译器强制、形式化验证
同语言重构是第一维度上的改变,而 Rust 的所有权模型是在第二维度上提供更强默认保证的方法。Rust 重写同时改变两个维度,但改善并不要求二者同时变化。项目可以在 C/C++ 内提高验证水平,只把特定模块替换为 Rust,或从新组件开始采用 Rust。
4. 限制完全替代的生态条件
- 技术约束:对 C ABI 的依赖 现代操作系统、硬件驱动和库把 C 语言调用约定作为标准接口。Rust 若要与既有生态互操作,也必须使用 C ABI。这意味着 Rust 与 C 生态存在长期“共存”和“连接”的结构性关系,而不是能够立即“替代”它。
- 市场约束:既有应用生态 软件市场的价值不仅来自语言,还来自以该语言创建的应用、数据格式、插件、用户工作流和运维知识。数十年间以 C/C++ 积累的商业与开源资产,包含仅凭技术特性难以消除的转换成本。
- 时间约束:必须在持续服务的同时完成替换 多数组织无法在新实现完成之前停止产品开发和事故响应。若重写团队必须持续追赶旧系统新增的功能和安全修复,目标会不断移动,新旧实现的差异也可能累积。
- 组织约束:知识与责任的转移 更换语言不仅是语法改变,还要重构招聘、培训、代码审查、调试、部署、事故响应和长期维护责任。不同组织的转换能力并不相同。
5. 对遗留系统的道德化与虚假类比
某些话语不把遗留系统看作技术状态,而把它规定为道德上的恶:
“说遗留系统不坏,就像说有害物质不坏一样。”
这是虚假类比(false analogy)。有害物质可以依据其对人体的生理影响评价,而“遗留”是描述系统历史与当前组织位置的关系性术语。遗留系统的风险应依据支持是否结束、漏洞、变更成本、事故记录以及是否符合当前要求判断,而不是依据年龄本身。
这种道德化会产生以下问题:
- 替换评价标准:不比较缺陷率、运维成本和转换风险,而用“好/坏”的价值判断预先取得结论。
- 按年龄泛化:排除旧系统积累的验证资产和兼容性,把“年老”当作存在缺陷的充分条件。
- 人身攻击与守门:把选择某项技术的人规定为异常或无能,但这不能证明技术选择是否合理。
- 技术单一主义:如“后端只能使用 Rust”,忽略问题领域、生态和运行条件,把一项技术定义为普遍答案。
此外,把 JSP 和 PHP 按通常意义归类为“前端技术”并不准确。JSP 是在服务器处理请求并生成响应的 Java 技术,PHP 也是主要用于服务器端脚本的通用语言。31 这些技术是否适合现代项目需要单独评价;分类错误和侮辱不能替代评价。
总之,Rust 是能有力阻止特定缺陷类型的重要工具,但不能从这一优点推出“所有 C/C++ 改进都没有意义”或“只有全面重写才合理”。这类结论与其说是工程比较,不如说是结合了范畴错误、虚假二分、完美主义谬误和单一指标还原的话语主张。
8.3 技术话语的历史先例:1990—2000 年代的操作系统竞争
围绕特定技术形成的叙事和集体身份并非 Rust 独有,而是技术史中反复出现的模式。一个例子是 1990 年代至 2000 年代初的 Linux 与 Microsoft Windows 竞争格局。
当时 Linux 社区中有多种声音并存,其中一股围绕“自由与共享”价值形成了叙事。参与者把自己视为对抗“巨型垄断企业”的技术与道德替代,并有时把 Microsoft 称为“M$”。32 这一叙事形成过程中出现了类似模式:
- 对立结构:使用“开放/封闭”“黑客文化/商业主义”等二元框架。
- 技术优越感:熟练使用文本 CLI 和编译内核被视为“真正开发者”的能力,并成为区分依赖 GUI 用户的标准。
- 对批评的回应:易用性或硬件兼容性问题被归咎于用户“努力不足”或“理解不足”,例如“RTFM(Read The Fucking Manual)”。33
- 对未来的乐观主义:不论客观市场份额如何,社区内部共享着“Linux 桌面之年(Year of the Linux Desktop)”终将到来的胜利信念。
这一历史案例显示,当特定技术社区的话语除技术特征外还围绕价值和身份形成时,可能出现何种现象。它提示我们,在分析 Rust 社区的部分现象时,除个人心理特征外,也可以采用技术社会学视角。
8.4 对回应批评的话语论证模式分析
围绕特定技术形成话语的社区,有时会对相反的批评话语呈现特定回应模式。本节通过若干论证结构示例进行分析。这些模式可见于比较多种技术的技术博客评论,以及 X、Hacker News、Reddit 等在线平台。本节目的并非考证某一事件的全部事实,而是把公开讨论中的论证结构与附录中的逻辑谬误联系起来加以示例。
案例研究 1:对客观数据的回应
情境:某网络论坛展示了使用 cloc 分析所得的客观数据:Linux 内核中的 Rust 代码占比不足 0.1%。有人据此批评“Rust 将替代所有系统编程”的主张存在现实局限。
观察到的回应模式:部分用户倾向于以下方式回应。
- 转移话题(red herring):不直接反驳“Rust 占比低”这一核心,而是说“像 Ada 这样的其他语言甚至没进入内核”,转移讨论对象;或者以“批评者支持某种语言,因此有偏见”为由质疑其动机。34
- 人身攻击(ad hominem):不是讨论批评内容,而是提及提出批评者的智力或人格,如“你没有理解这种逻辑的智力”或“看你的态度就知道你的水平”。35
- 提出其他案例:不回应 Linux 内核占比这一具体数据,而选择性地提出“Google、Microsoft 等巨头使用 Rust”,试图维护原主张。这可能涉及“摘樱桃”或草率概括谬误。
分析:上述回应模式属于已知逻辑谬误类型。它表明,当基于客观数据的批评与既有叙事冲突时,可能出现不直接回应数据的其他反应。
案例研究 2:“安全性”定义的边界与讨论
情境:某开发者指出,Rc<RefCell<T>> 循环引用造成的内存泄漏可能在长期运行的服务器应用中引发问题。这与 3.2.4 节相连。
观察到的回应模式:部分用户倾向于聚焦术语的“定义”。
- 定义论证(argument by definition):“Rust 的‘内存安全’按定义是不存在未定义行为(UB)。内存泄漏不是 UB,因此与 Rust 的安全保证无关。所以你的观点偏离主题。”他们以语言的官方技术定义为依据。
- 责任归属:“制造循环引用是开发者的错误,Rust 提供了
Weak<T>等解决方案。不能因为开发者未正确使用工具提供的功能,就把责任归于语言的限制。”问题原因被归于开发者个人。
案例研究 3:有关“智识诚实”的讨论与社区冲突
情境:某非营利安全基金会公布了一个把 C 语言视频解码器移植到 Rust 的版本,并为性能改进提供奖金,引发争议。
其中的技术争点和冲突可概括如下。
- 性能与“安全性”主张:Rust 移植版本提到“内存安全”,但实际性能来自原 C 项目的汇编代码。这些代码通过绕过 Rust 安全检查的
unsafe块调用。 - 有关“智识诚实”的批评:以原 C 解码器开发者社区为中心,有人批评:“性能的真正来源是 C/汇编,却宣传为‘安全 Rust’的成果,没有公平承认原项目的贡献。”
- 维护模型:Rust 移植版本需要手工回移原 C 项目的更新。C 开发者社区批评这是不对称贡献结构(asymmetrical contribution structure):核心研发依赖原 C 项目,却只利用其成果。
案例研究 4:CVSS 10.0 漏洞与“内存安全”讨论
情境:2024 年 4 月,Rust 标准库(std::process::Command)中发现了 CVSS 10.0(Critical)级命令注入漏洞 CVE-2024-24576。这是在“安全”Rust 代码中发生的安全缺陷。
观察到的回应模式:部分网络话语认为,该事件并不损害 Rust 的“安全性”保证。
- 把论点限定为“内存安全”:使用了“这是一个 bug,但不是内存安全漏洞”的逻辑。该 CVE 属于逻辑错误(CWE-78),而非缓冲区溢出等内存错误。
- 提及外部因素:有人引用 Rust 官方博客关于“漏洞原因在于
cmd.exe的复杂性”的说明,把问题原因归于 Windows 操作系统 API 的设计。
案例研究 5:unsafe 缺陷与“定义论证”的结构
情境:Linux 内核 Rust Binder 实现中发现了因 unsafe 逻辑设计错误造成的数据竞争漏洞 CVE-2025-68260。这是一个原本希望通过静态分析保证的线程安全性,在系统驱动实现层面未得到明确维持的案例。
观察到的回应模式:漏洞公开时,技术社区内部话语呈现以下逻辑结构。
- 责任个体化:错误被规定为实现
unsafe块的开发者的逻辑错误,而非语言规范缺陷。由此保持 Safe Rust 区域的完整性。 - 差异化应用比较标准:C/C++ 中的类似错误倾向于被解释为语言设计的内在风险,而 Rust 的
unsafe错误则倾向于被分析为个别开发者的熟练度问题。
分析:这种话语形成具有本书所定义的“定义论证(argument by definition)”结构。其逻辑机制是:“安全区域不会发生错误,而发生错误的地点按定义属于不安全区域,因此 Rust 的安全保证模型仍然有效。”
这种结构体现了技术共同体维护特定技术完整性的倾向。把漏洞原因限定为“个别开发者疏忽”,而非语言本身,会限制对一项设计权衡的讨论:系统实现不可避免地要求使用 unsafe,也因此不可避免地存在人为错误可能性。
案例研究 6:“除了 Rust 别无选择”与需求的省略
情境:某网络论坛上的一项主张大意是:“在无法自由决定部署环境的实战项目中,我没有信心使用 C++,而且除了 Rust 别无选择。”这项主张以 C++ 的内存安全风险和部署约束为依据,进而得出 Rust 具有唯一性的结论。
论证结构:把这项主张简化后,可以写成:
- C++ 不是内存安全语言,因此在实战项目中难以选择。
- 部署环境存在约束。
- 因此,除了 Rust 别无选择。
第一项前提可能足以成为在特定项目中排除 C++ 的理由。但是,排除一个候选项,并不意味着其他所有候选项也都已被排除。要使第三项结论成立,至少还必须说明以下事项。
- 目标系统对性能、延迟、内存、实时性、认证以及支持平台的要求
- 是否允许 GC、VM 或托管运行时,以及单一二进制文件、静态链接等实际部署条件
- 既有代码、库、ABI、运维工具和组织人员方面的约束
- 参与比较的候选集合,以及按照同一标准排除各候选项的依据
候选项会随这些条件而变化。如果后端或业务系统允许托管运行时,Java、C#、Go、Python 和 Ruby 都可能成为候选项(3.5 节)。如果核心要求是无 GC 的原生执行和编译时内存安全保证,Rust 是强有力的候选项;如果重视强运行时检查、可预测的实时性和形式化验证,Ada/SPARK 也应纳入考虑(3.4 节)。如果既有 C/C++ 资产规模很大,在原语言内进行现代化、隔离高风险模块、选择性引入 Rust 以及采用混合语言架构也都是备选方案(3.3 节)。根据平台和生态的不同,Swift 等其他语言也可能更合适。
此外,“实战项目”不是需求,而是一种修辞性范畴。Web 服务、桌面应用、嵌入式设备、操作系统内核、金融系统和安全关键控制系统都属于实战项目,但它们的缺陷模型和部署条件并不相同。用“实战”一词抹去这些差异,会把从某种特定经验得出的判断扩大为适用于所有项目的规则。
部署约束同样不会自动选出 Rust。在禁止安装运行时的环境中,Rust 可能具有优势;但在只批准 JVM 或 .NET 的组织、固定使用特定 ABI 和供应商工具链的设备,或要求经过认证的 Ada 工具链的系统中,其他选择可能更现实。部署约束越强,就越需要审查引入新语言和新工具链本身是否会成为额外约束。
判定标准:要证明“除了 Rust 别无选择”这一强命题,必须 (1) 明确需求,(2) 构造现实的候选集合,(3) 对所有候选项应用相同的比较标准,并且 (4) 证明 Rust 满足这些需求,而其他候选项不满足。若没有这种比较,仅凭对 C++ 的不安和含糊的部署约束,所证明的不是 Rust 的唯一性,而是候选集合被省略以及虚假二分。
更准确的命题是:
如果一个项目同时需要无 GC 的原生执行、强静态内存安全、目标平台支持以及组织长期维护 Rust 的能力,并且其他候选项都无法满足这些条件,那么 Rust 可能是最强的选择。但是,在这些条件和比较被明确展示之前,必须区分“有力的选择”与“唯一的替代方案”。
8.5 技术选择的地位化:资格、正常性、智力等级与话语排除
在技术讨论中,发言者的经验和专业能力可以帮助判断证据所支持的范围。评估某种语言在什么条件下是更强的选择,以及某个人是否具备实际使用该语言的能力,也属于正当的工程判断。但是,当技术优势被转换为选择该技术者的一般优越性,并被用作贬低非使用者智力、资格或正常性的依据时,讨论便从技术评价转向对个人和群体地位的判定。
本书把这种转移称为技术选择的地位化。这并不是一个得到广泛共识的单一心理学术语或形式谬误名称,而是用于分析公开技术话语中以下连续过程的描述性范畴。
- 某种技术的条件性优势被扩大为普遍优越性。
- 理解或选择该技术的事实,被用作用户在智力或职业上优越的标志。
- 非使用者不再只是处于不同条件而作出不同选择的人,而被分类为无知或落后的外群体。
- 技术反论不按其内容审查,而被解释为发言者的低下、恐惧、沉没成本或理解不足。
- 当反对和不快也被吸收为确认既有地位分类的证据时,论证便形成自我封闭结构。
本节不推测特定个人的性格、精神状态或临床特征。分析对象是公开发言中可以观察到的分类标准、举证责任和论证结构。本节中采用引语形式的句子也不是对某个具体个人发言的逐字引用,而是为了清楚呈现公开技术讨论中反复出现的结构而重构的合成示例(composite example)。因此,这些示例用于分析此类论证可能存在的方式,并不是可据以推断其出现频率或 Rust 用户整体态度的统计样本。
1. 区分专业性审查与排除发言者
指出专业知识不足,并不总是守门。对于特定 unsafe 代码的健全性、编译器实现或操作系统内核接口等需要高度专业知识的问题,确认相关经验和证据质量是正当的。不过,在以下情况下,专业性审查可能不再是评价论点的手段,而变成排除发言者的装置:
- 仅根据与批评内容无关的所属组织、惯用语言或职称否定主张;
- 不检查所提交的资料或可复现结果,直接以资格不足作结论;
- 只有在反例出现后,才重新设定“真正的开发者”或“真正的系统编程”的标准;
- 对质量相当的证据,向支持者和批评者适用不同的专业性标准。
合成示例:“你的项目没有自行实现事件循环或调度器,所以不是真正的系统编程。因此,你关于生态和生产率的经验没有评价价值。”
这种回应没有先说明评价该问题究竟需要何种经验,而是把发言者的工作排除在相关类别之外,从而消除批评的效力。不过,并非所有范围划分都属于“真正的苏格兰人谬误(No True Scotsman Fallacy)”。这一谬误通常要求先对某个群体作出一般化陈述,随后出现与该一般化相冲突的反例,再为排除反例而任意缩小群体定义。36 如果无法确认这种先行的一般化和事后的重新定义,该发言仍可能构成守门或不正当的范围限制,但不应立即断定为“真正的苏格兰人谬误”。
2. 技术优越性与用户地位的结合
某种语言在特定条件下比其他选择更安全或更有生产率,并不包含对用户个人一般智力或人格价值的判断。关于技术的命题与关于选择该技术之人的命题,需要不同的证据。尽管如此,某些话语仍会发生如下转换。
合成示例:“有能力理解这门语言根本原理的开发者,最终都会选择它。坚持其他选择的人,要么思维水平较低,要么被过去的职业经历所束缚。”
在这种结构中,技术优越性与用户优越性以循环方式相互保证:因为是优越的人,所以选择优越的技术;因为选择了该技术,所以被归为优越的人。然而,语言选择可能随需求、既有代码、人员、工具、认证、部署环境、实时约束、迁移成本和组织风险容忍度而变化。理解同样事实的开发者,在不同条件下仍可能得出不同结论。
当技术开始充当用户地位标志时,技术批评的意义也会发生变化。对语言性能、安全保证或生态的批评,可能不再被理解为审查某项主张的适用范围,而被解释为否定选择该语言的用户及内群体的洞察力、先驱性和职业价值。结果是,回应不再回答技术反论,而更多转向评判批评者的资格和心理。
这种分析并不把强烈的技术偏好或社区归属感本身视为问题。如果在积极推荐技术的同时明确适用条件和反例,并独立评价非使用者的能力和价值,那么技术主张与用户地位仍然分离。问题发生在一种技术选择被用作衡量整个人之等级的替代指标时。
3. 正常性的规定与内外群体的构造
本书把在缺乏独立依据的情况下,将某一生态的惯例作为普遍规范的修辞模式,描述性地称为“规定正常性(defining normality)”。它并不是一个得到广泛共识的单一形式谬误名称。
“正常”“标准”“常见”等词本身并没有错误。如果根据国际标准、明确的组织规则、市场占有率、兼容性要求或经过测量的使用频率来限定范围,它们可以成为有用的技术描述。问题发生在评价标准和总体范围没有说明,却把发言者熟悉的生态选择说成所有语言和组织都应遵循的默认值时。
合成示例:“正常的语言就应当以同一种既定方式提供构建、包管理、代码分析和编辑器支持。无法接受新标准的开发者已经属于落后的一代。”
这个示例在同一段话中把技术配置的正常性与用户的正常性结合起来。第一句提出工具配置标准,第二句则把不遵循该标准的人归入落后的外群体。此时,“正常”“现代”“真正”“负责任”等词不仅描述技术属性,还承担规定内群体资格和外群体缺陷的作用。
现实开发环境同时存在以集成 IDE 为中心、以独立语言服务器为中心、以命令行工具为中心以及组织内部平台为中心的模型。各模型在安装便利性、自动化、可替换性、离线运行、长期支持和组织控制方面具有不同成本。因此,应比较的是何种选择适合哪些用户和运行条件,而不是宣布某一种工具配置或语言能够普遍证明其用户现代或正常。
4. 区分语言熟练度、认知效果与工作绩效
某些话语把学习特定语言的能力或意愿,直接与一般智力及程序员的职业胜任力联系起来。
合成示例:“没有能力学习 Rust,或者不愿意学习 Rust 的人,最终也不具备职业程序员的资格。”
这一主张把四个不同问题合并为一个问题。
- 学习与认知任务迁移:编程教育不仅包括语言概念和工具的学习,也包括部分问题解决策略。综合 105 项研究和 539 个效应量的元分析报告,编程学习具有中等程度的总体迁移效应(
g = 0.49)和远迁移效应(g = 0.47)。这支持编程教育可能对某些认知任务产生积极影响。37 - 一般智力与人的等级:特定任务成绩提高或学习迁移本身,并不能证明心理测量意义上的一般智力全面上升,也不能证明学习者与非学习者之间存在人格或职业等级。更强的结论需要独立的测量和研究设计。
- Rust 特有效果与选择效应:上述元分析综合了多种编程语言和教育环境,并没有检验 Rust 独有的认知效果。同样,即使自愿选择 Rust 的群体表现出更高的既有知识或技术兴趣,也必须控制教育机会、工作经验和自我选择,才能断定是语言学习造成了差异。
- 工作相关性:当编写和维护 Rust 代码是核心工作时,Rust 熟练度或学习能力可以成为直接的选拔标准。对于其他语言、领域或职能的岗位,则应分别评估设计、调试、测试、运维、安全、协作和领域知识等实际工作所需能力。作为一个方法论参考,美国《雇员选拔程序统一指南》要求把选拔标准的效度与重要工作任务及其所需知识、技能和能力相联系,而不能仅凭声誉或轶事假定其有效。38
因此,“熟练使用 Rust”可以证明某些与 Rust 有关的能力,但其本身并不能代表所有软件开发工作的绩效或一般智力。反过来,尚未学习 Rust 或在学习中遇到困难,也不能据此否定一个人成为程序员的可能性。
5. 技术反论的心理化与动机推测
心理和社会因素确实可能介入技术选择。既有教育和代码投入、组织声誉或个人职业经历可能形成使选择难以改变的沉没成本;对新技术的不确定性或既有地位的变化也可能影响决策。但是,如果没有具体证据,就不能把这些可能性断定为某项反论的原因,也不能仅凭动机推测否定反论的内容。
合成示例:“他们无法承认旧语言的局限,是因为害怕过去投入的职业经历和代码变得毫无价值。他们提出技术理由,但真正的动机只是维护自身地位的抵抗。”
这种回应没有审查性能、生产率、生态、认证、招聘和既有系统集成等争点,而是用对发言者动机的推测加以替代。即使沉没成本或地位防卫在一定程度上存在,也不能因此证明“应维护既有系统或渐进迁移”的技术主张为假。动机是否存在与主张内容真假,应分别评价。
动机推测还必须对称适用。如果不能仅因批评者投资于既有技术而否定其主张,也不能仅因支持者投资于新语言的学习、项目、声誉和群体身份而否定其主张。无论哪一方,都应先检查代码、测量结果、成本、风险和适用范围。
6. 偏好技术的普遍化与重复认可
当技术选择与用户地位结合时,某项技术的实际优势可能被扩大到原有适用范围之外。内存安全方面的强项可能被推导为“它应成为所有软件的默认语言”,某项服务或新组件的成功可能被推导为“所有既有系统都应重写”。此时,偏好的技术不再被当作多种工程手段之一,而被当作可反复应用于各种问题的默认答案。
合成示例:“安全、性能、生产率、可维护性和开发者水平的问题,最终都来自同一个原因。把这门语言设为默认值,就能同时替换落后的技术和落后的思维方式。”
不同问题需要不同的比较标准和证据。内存安全、吞吐量、延迟、可认证性、开发生产率、人员供给和遗留系统集成,不能缩减为单一指标。把一个领域的强结果转移为另一个领域的优越性,需要补充证据。产业案例和政策资料的适用范围将在 8.7 节另行审查。
当同一主张和地位分类在同质群体内部得到重复认可时,这种重复可能看起来像主张本身的证据。但是,内群体的共识或反应规模,不能替代说明结论在哪些总体和条件下成立。重复认可必须与技术验证区分开来,并应保留审查外部反例和不同成本结构的渠道。
7. 自我封闭论证
本书所说的自我封闭论证(self-sealing argument),是指把反对证据和反应也吸收为原有结论的进一步证据,从而使任何观察都无法削弱结论的论证结构。这里使用这一名称是为了进行描述性分析,并不意味着所有心理动机推测都会自动构成同一种错误。
合成示例:“智力正常的程序员都会承认 Rust 的优越性和学习价值。反对的人是缺乏理解能力,而对这种评价感到不快,恰恰证明他知道自己的低下。”
这一论证具有如下封闭结构。
- 预设结论:把 Rust 的普遍优越性和支持者的正常性当作分类前提,而不是需要检验的结论。同意成为正常的证据,反对成为低下的证据。
- 技术与用户地位的循环保证:因为是优越的人,所以选择 Rust;因为选择了 Rust,所以被归为优越的人。两者都没有经过独立测量的检验。
- 把技术反论心理化:性能、生产率、生态、认证、招聘或与既有系统集成等反论,被替换为对自卑、恐惧、沉没成本或抵抗等动机的推测。
- 消除可证伪性:如果反对、不快、漠不关心以及尝试说明都被解释为同一结论的证据,任何观察都无法使主张得到修正。
- 不对称的举证责任:要求批评者承认该语言的全部优点并克服学习障碍,却不要求主张优越的一方提供比较资料、适用范围和失败条件。
- 混淆条件性技术判断与人的等级:在内存安全和性能同时重要的领域,Rust 可以是强有力的选择,在这些条件下承担学习成本也可能值得。但这不能推出所有程序员都必须选择同一种技术,也不能证明不同选择意味着智力低下。
8. 审查地位化主张的标准
评价技术讨论中有关资格、正常性或用户地位的主张时,需要区分以下问题:
- 判断该主张真伪实际上需要哪些专业知识?
- 该专业性标准是否在反论出现之前已经说明?
- 是否独立于发言者背景,检查了代码、资料、测量结果和复现步骤?
- 关于技术优势的证据,是否真的测量了用户的一般智力或职业优越性?
- 是否没有从需求和成本出发,而只用无知、低下、恐惧或沉没成本解释非使用者的选择?
- 是否对支持者和批评者适用相同的证据、专业性和动机推测标准?
- 是否有额外证据把某一领域的成功扩大到其他问题和所有既有系统?
- 是否区分了内群体的重复同意与独立技术验证?
- 出现什么反例或结果时,会修改现有判断?
- 如果评价语言熟练度,它与该岗位的重要工作任务有什么联系?
总之,专业能力、标准、语言熟练度和技术建议都可以成为正当的评价因素,但必须说明其适用范围和证据。把某种语言的优势转换为其用户的一般优越性,把非使用者分类为低下的外群体,并仅用发言者的缺陷解释反对意见的话语,会在检查技术事实和工作相关性之前先决定人的地位。
技术不是替代性衡量用户智力或人格价值的标志。承认强有力的技术成果,与拒绝把这些成果扩大到所有问题、所有组织以及所有开发者的等级并不矛盾。分离技术选择地位化现象的目的,不是贬低某个具体社区,而是把条件性的工程判断还原为可验证的主张。
8.6 2023 年商标政策争议与治理思考
开源项目在成长和制度化过程中,既有非正式惯例与新正式政策之间可能发生冲突,并促使人们审视治理模式。2023 年 Rust 商标政策草案引发的争议,是这一过程的案例研究。
2023 年 4 月,Rust 基金会公布了有关 Rust 名称和标志使用的新商标政策草案,并请求社区反馈。但草案被认为比社区既有非正式惯例更为限制,引发批评和反弹。主要担忧是,政策可能限制社区活动、项目名称和 crate 名称使用 Rust 商标,从而压缩生态活动。39
争议产生了几个结果。
第一,社区反弹使名为“Crab-lang”的语言 fork 可能性被公开讨论。这说明对政策的不满可能发展为项目分裂风险。
第二,该事件揭示了 Rust 基金会与构成项目的开发者社区之间在沟通方式和认知上的差异。有人批评基金会在履行商标保护的法律责任时,没有充分考虑社区长期维持的文化和价值。
最终,Rust 基金会接受社区反馈,撤回草案,并表示将与社区共同重新制定政策。40
该事件被记录为对 Rust 项目领导层与社区之间的信任关系及治理模式提出问题。它展示了开源项目建立正式治理结构的过程,以及在这一过程中与社区沟通和形成共识的必要性。
8.7 政府建议与产业成功案例的引用范围
在论证特定技术时,政府机构建议、企业采用案例和量化成果常被用来增强主张的正当性。Rust 话语有时把转向内存安全语言的建议与 Android、Discord、Cloudflare、AWS、Linux 内核等案例结合成一条连续证据,用以支持“Rust 已成为所有系统默认标准”的结论。本节区分各资料实际支持的范围,以及从个别案例推导普遍技术规范时还需要哪些证据。
1. NSA 提出内存安全语言清单(2022—2023)
2022 年 11 月,美国国家安全局发布题为“Software Memory Safety”的信息报告,强调在软件开发中确保内存安全的重要性,并建议转向内存安全语言。报告明确列出 C#、Go、Java、Ruby、Rust、Swift,2023 年 4 月更新又加入 Python、Delphi/Object Pascal 和 Ada。41
该报告后来被用作证据:讨论国家安全级可靠性的机构把 Rust 与其他内存安全语言归入同一类别。
2. 白宫呼吁转向内存安全语言(2024)
2024 年 2 月,美国国家网络总监办公室发布报告,强调技术生态转向内存安全语言的必要性。42 报告指出 C/C++ 等内存管理不安全语言产生的漏洞对国家网络安全构成严重威胁,呼吁开发者默认采用内存安全语言。它没有提供具体语言清单,但把 Rust 作为内存安全语言的一个例子。
3. 通过选择性解释连接两份报告
两份报告的目的、内容和发布时间不同,因此可能被选择性地连接和解释,以构造特定逻辑。
- 前提 1(NSA 报告):技术机构给出了具体的内存安全语言清单。
- 前提 2(白宫报告):国家最高行政机构宣告转向内存安全语言是紧迫的国家任务。
- 推断与筛选:依据两项前提,从 NSA 清单中筛选符合“系统编程”目的的语言。
- Python、Java、C#、Go、Swift 等使用 GC 的语言,常因“运行时开销”被判定为不适合系统编程并从讨论中排除。
- NSA 清单中的非 GC 语言 Ada 在这一过程中被省略或不受重视。
- 得出结论:经过选择性筛选后,结论变成:“在 NSA 的安全语言清单中,能够无 GC 完成白宫所要求的系统编程内存安全任务的唯一现实替代方案是 Rust。”
这一推断展示了:目的和语境不同的材料如何被连接,以及选择性应用“无 GC”等标准如何生成符合初始框架的结论。
4. 产业成功案例的因果归属与外部有效性
产业案例是证明技术选择有效的重要证据。但替换语言前后,数据结构、架构、并发模型、连接复用、部署方式、硬件、团队熟练度和观测指标也可能一同变化。要把全部成果归于语言本身,需要说明相同工作负载和基准、同时改变的因素、比较周期以及反事实替代方案。
- Android 的安全成果:Google 报告称,Android 内存安全漏洞占比从 2019 年的 76% 降至 2024 年的 24%,2025 年首次低于 20%。2025 年分析基于约 500 万行 Android Rust 代码中在发布前发现并修复的一项潜在漏洞,估计其内存安全漏洞密度比历史 C/C++ 数据低 1,000 倍以上。43 44 这证明 Rust 在 Android 新增和活跃开发代码以及高风险边界中取得强成果。但总体下降还受包括 Java、Kotlin 在内的内存安全语言策略、既有代码成熟、沙箱和防御层影响,不能把全部下降归于 Rust。
- Discord 延迟案例:Discord 报告称,把特定 Read States 服务从 Go 重写为 Rust 后,周期性延迟尖峰消失,延迟、CPU 和内存指标改善。原文说明了 Go 1.9.2 系列实现、巨大 LRU 缓存和每两分钟强制 GC 的具体条件,并明确不主张把所有系统重写成 Rust。原文也没有给出 P99 延迟降低 50% 的数字。45
- Pingora 的资源节省:Cloudflare 以相同流量比较旧 NGINX/OpenResty 服务,报告 Pingora 少用约 70% CPU 和 67% 内存。官方说明把原因同时归于 Rust 代码效率、多线程架构、消除 C–Lua 边界复制以及提高连接复用率。特别是延迟改善主要来自使用共享连接池的新架构,而不是代码执行速度。46 因此,这是“以 Rust 实现的重新设计”的成果,而不是只改变语言的受控实验。
- Firecracker 启动时间:AWS 公布的低于 125 毫秒启动时间,是在
i3.metal、默认 microVM 大小和最小设备模型条件下的 Firecracker 系统成果。AWS 的确因内存和线程安全选择 Rust,但这些数据没有把启动时间分离为 Rust 语言的独立性能效果。47 - Linux 内核的持续采用:2025 年 Linux Kernel Maintainers Summit 后,Miguel Ojeda 说明 Rust 支持实验结束,Rust 将继续保留。同一说明也指出,并非所有内核配置、架构和工具链组合都已完成,仍有大量工作和实验性组合。48 这证明 Rust 支持的持续性,但不意味着内核整体默认实现语言已转为 Rust。
5. 数字、漏洞与路线图的来源追踪
量化主张中的数字越大、越具体,越显得有说服力;但调查年份、总体、分母、漏洞描述和路线图状态变化时,结论也会变化。
- 生态规模:226.7 万开发者是 JetBrains 根据 2024 Developer Ecosystem 数据估计的过去十二个月 Rust 使用者数量。49 2025 State of Rust Survey 收集了 7,156 份回答,并明确警告不要从约 7,000 份回答过度外推整个社区。50 两项资料的测量目的和总体不同,不能作为同一共同统计引用。
- CVE 归属:CVE-2025-30388 的官方描述是 Windows Win32K-GRFX 堆缓冲区溢出,需要本地执行和用户交互。官方记录没有把代码归于 Rust,因此不能用它支持“Windows 内核 Rust 代码的首个远程代码执行漏洞”这一说法。51 相比之下,CVE-2025-68260 是 Linux Rust Binder 中对
unsafe列表删除操作的并发访问造成数据竞争和指针损坏。52 该案例要求审计unsafe契约和共享状态,但既不支持“Safe Rust 的保证不存在”,也不支持“影响会自动限制在unsafe块内部”。 - 异步功能状态:Rust 项目的 2026 路线图说明当前仅有同步析构函数,并指出异步清理的需要,把实现保证析构和 async drop 的基础列为 2026—2027 年探索事项。53 这是探索方向,不是确认会包含在某个“2027 edition”中的承诺。
6. 区分政策建议与“标准”宣言
NSA 与 CISA 的 2025 年联合指南建议采用内存安全语言,以直接改善软件安全。54 它尤其为把内存安全作为处理外部输入的新高风险代码的默认要求提供了有力依据。但建议对象是多种内存安全语言;适合的语言和变更策略会随实时性、认证、生态、硬件、既有资产和转换风险而变化。
DARPA 的 TRACTOR 计划研究把遗留 C 代码自动翻译为达到熟练开发者水平的 Rust。55 这说明 C-to-Rust 转换具有国家级研究价值,但不是所有代码已经可以经济、自动转换的证据,也不是研究结果的预先保证。
因此,“已经成为标准”至少要区分三种含义:
- 安全要求的标准:政府指南和产业资料有力支持把内存安全视为新高风险软件的默认要求。
- 工具选择的事实标准:Rust 是否在特定领域和组织中成为主要选择,可以通过采用率、持续使用时间和与替代方案比较来检验。
- 所有系统的规范性默认值:主张所有不选择 Rust 的情况都必须证明例外,需要另行论证适用领域、替代语言、既有系统风险和转换成本。
承认强劲的产业成果和政策方向,同时不在证据范围之外宣布普遍义务,二者并不矛盾。对内存不安全的举证责任可以加强,但这并不自动使所有软件语言选择收敛为一种语言。
8.8 话语背后:官方改进努力与共同体治理
本章分析了部分支持者回应特定技术批评时呈现的防御性话语模式。这些现象并不代表 Rust 生态的全貌。与非官方话语并存的,还有承认 Rust 技术限制并试图改进的官方努力。
Rust 项目的特点之一,是以 RFC(Request for Comments)流程为代表的治理模式。语言变更和新功能提案通过 RFC 文档公开讨论。开发者在此过程中讨论技术有效性、潜在问题和与既有生态的兼容性,再作出最终决定。这是接纳批评以发展技术的文化案例。
此外,Rust 开发者和多个工作组也把本书指出的技术挑战设为改进目标并寻找解决方案。例如,开发者通过博客承认 async 模型的复杂性和学习曲线问题并提出改进愿景;缩短编译时间也是编译器团队持续研究与开发的任务之一。
总之,理解一个技术生态时,应区分非官方网络空间中部分人的防御性声音与项目通过官方渠道推进的改进努力。Rust 生态中存在这种正式反馈循环,可以被视为该技术潜力和发展能力的证据。
第五部分:综合分析与结论
第五部分基于技术分析和生态现状,分析 Rust 的效用与约束。
第 9 章重新评价 Rust 的技术优势与限制、开发者能力模型和社区文化。第 10 章提出生态成熟与扩展的任务,并提出技术选择的分析框架,结束讨论。
9. 重新评价 Rust:效用、约束与技术选择策略
第 9 章根据前述技术特性和生态现实,综合分析 Rust 的效用与约束。
9.1 节考察编译期内存安全保证这一技术特性如何在工业现场使用,以及 Rust 在市场中的位置。9.2 节分析技术偏好话语与实际就业市场的关系,并考察 Rust 的抽象层级对开发者能力的影响。9.3 节讨论社区文化和反馈循环对技术生态可持续性的作用。
9.1 Rust 技术特性与适用领域分析
1. 优势:编译期内存安全保证
Rust 的技术特点之一,是在语言和编译器层面防止特定类型的内存错误。C/C++ 中常导致安全漏洞的缓冲区溢出、释放后使用和空指针解引用等问题,会通过 Rust 的所有权与借用检查器模型在编译期静态分析并阻止。
这把软件安全范式从“运行时检测和防御错误”转向“编译期从源头防止错误”。安全代码成功编译后,可以对这些类型的内存漏洞不存在提供保证。
这种内存安全不仅有助于阻止系统控制权被夺取,也有助于防止敏感信息泄露。2014 年 Heartbleed 漏洞展示了缺失边界检查如何导致信息泄漏。Rust 默认对数组和向量访问执行边界检查,并通过所有权系统禁止访问已释放内存,从结构上降低此类错误的可能性。
Microsoft、Google 等公司曾分析其产品中的安全漏洞约 70% 源于内存安全问题。56 57 这些外部环境分析展示了 Rust 结构性安全保证的效用。
2. 适用领域:性能与可靠性交汇处
Rust 的技术特性用于云原生基础设施和网络服务。这些领域既要求在没有垃圾收集器暂停的情况下维持低延迟,也要求抵御外部攻击的安全性和可靠性。
-
案例研究 1:解决 Discord 的性能问题
Discord 在 Go 服务中经历了 GC 导致的延迟尖峰。实时通信中,这种延迟会影响用户体验。团队把 Read States 等后端服务重写为 Rust,消除 GC 暂停,同时避免 C++ 手工内存管理的风险并保持内存安全。这是 Rust 被用作 GC 约束替代方案的案例。58 -
案例研究 2:Linkerd 的代理实现
服务网格项目 Linkerd 使用 Rust 实现数据平面代理linkerd-proxy。代理部署于整个基础设施,需要低资源占用、速度、可靠性和安全性。Rust 通过零成本抽象提供接近 C/C++ 的性能和较低内存使用,并以编译期安全保证降低基础设施组件中的漏洞风险。这表明 Rust 用于同时要求性能和安全的系统组件。59
此外,Cloudflare、AWS 等云企业在网络服务和 Firecracker 等虚拟化技术中采用 Rust,Figma 则在 WebAssembly 环境中使用 Rust 进行图形渲染。这些案例表明 Rust 在特定市场中得到实际应用。
3. 市场位置与局限
Rust 在同时要求性能与安全、且限制使用 GC 的特定领域,被用作既有语言的替代方案。
但这种利用并不会自动扩展到所有软件开发领域。
- 传统系统编程(C/C++):操作系统、嵌入式、游戏引擎等领域数十年积累的 C/C++ 代码资产和生态构成准入壁垒。
- 企业业务应用(Java/C#):大型企业环境除运行时性能外,常把开发生产率、库生态和人员供给作为评价标准。特别是在要求频繁变更业务逻辑并持续提供服务的 Web 后端中,垃圾收集器和异常处理方式可能比严格的内存管理更有利于生产率和可用性。
因此,Rust 当前可被分析为解决特定市场问题的“专用工具”。若要作为通用语言成为市场主流,还需解决其他领域的技术与生态问题。
9.2 技术生态现实与多维开发者能力模型
Rust 的技术特性和生态现状与开发者的技术选择及能力发展策略相关。
1. 技术偏好话语与实际就业市场之间的差距
在 Stack Overflow 开发者调查等资料中,Rust 多次进入“最受喜爱的语言”项目,显示出开发者偏好。技术企业的采用案例也塑造了人们对语言潜力的认识。
但技术偏好话语与实际就业市场需求之间仍有差异。到 2025 年,Rust 开发者招聘需求虽在增长,但与 Java、Python、C++ 等市场规模相比仍占较小比例。
这种差距可以理解为企业采用新技术时综合考虑学习成本、生态成熟度和与既有系统集成成本的结果。开发者规划职业时,除了技术热度和潜力,还需考虑市场规模与生态成熟度。
2. 语言抽象层级与基础计算机科学知识的关系
Rust 的所有权与生命周期模型要求开发者理解内存管理原理,有助于培养系统编程能力。
但 Rust 提供的抽象也可能限制直接经历某些基础原理的机会。语言强制安全内存管理,因此开发者比起 C/C++,更少在手工 malloc/free 过程中直接经历并解决内存泄漏或重复释放等错误。
同样,使用 Vec<T>、HashMap<K, V> 等标准库结构,与在低级语言中亲自实现链表或哈希表并经历内存布局设计和指针操作,是不同层面的学习。
这说明单一语言学习无法涵盖计算机科学全部基础。通过低级语言直接实现内存管理和数据结构的经验,可以成为理解 Rust 等语言抽象价值及内部机制的基础。因此,独立于某项语言技术的熟练度,数据结构、算法、操作系统等基础知识仍然有效。
3. 工具依赖与防御性编码的关系
开发者能力模型还应包括对工具限制的认识。如 4.2 节所分析,“语言安全”并不意味着“所写代码安全”。Rust 编译器防止内存破坏型 UB,但不能阻止逻辑错误导致的服务中断、panic 或可用性下降。
依赖语言安全保证可能减少对异常情况的验证等防御性编码。因此需要理解语言保证的边界,并对边界之外的领域(逻辑错误、系统恢复力等)采用单独的验证与纪律。
4. 开发者能力与招聘评价的多维性
软件工程工作不只由语言语法熟练度构成。需求、架构、设计、实现、测试、安全、运维、维护、质量和专业实践彼此相连。IEEE Computer Society 的 SWEBOK Guide V4.0 把软件工程组织为 18 个知识领域,也不把一种编程语言当作整个职业的代理指标。60
招聘标准不会因为数量多或少而自动有效。过度细分或与实际工作无关的清单可能排除优秀候选人;反过来,只用一种语言熟练度预测所有角色的绩效也会造成信息损失和误判。每项标准应通过工作分析与实际任务连接,并尽可能使用工作样本、结构化面试和历史绩效资料检验预测有效性。人员选拔研究也把评价方法的有效性视为测量和验证对象,并指出某些传统估计可能被高估。38
招聘 Rust 开发者时,可以直接评价 Rust 编码、所有权理解和相关生态经验。但一般开发者评价需要按角色组合以下维度。
- 准确理解问题和需求的能力
- 设计、实现、调试和测试能力
- 处理性能、安全、可靠性和运维问题的能力
- 代码审查、沟通、协作和领域知识
- 学习新技术并迁移既有知识的能力
9.3 技术共同体、组织评价与生态可持续性
编程语言和软件组织的持续性,不仅与技术本身相关,也与社区文化和评价制度有关。接受批评的方式、对待新人态度、工作优先级、测量指标和奖励结构,会影响知识共享、人才保留和长期可维护性。
1. 批评与反馈循环的作用
外部批评和内部问题报告在技术生态中构成反馈机制。与 C++、Ada、Go 等具有不同设计哲学的语言社区讨论,为检视某项技术的特征与限制提供机会。
社区接受和处理外部反馈的方式与生态成熟度相关。部分网络讨论中可见的防御性态度可能减少技术交流;像 Rust RFC 那样把批评纳入正式流程的文化,则能促进生态发展。
2. 新参与者入门与知识共享文化的影响
技术生态可持续性与新参与者流入相关。Rust 项目正式设有行为准则。
但除正式方向外,部分网络论坛中可观察到两种回应初学者问题的模式。
- 指出知识不足:不回答问题内容,而是说提问者知识或努力不足(“先读官方文档”),或否定问题前提(“根本不需要这种做法”)。这可能延迟解决问题,并降低参与意愿。
- 提供信息与替代方案:理解提问者的困难,说明原因可能在技术复杂性而非个人能力,并提供解决信息或替代方案。这有助于新人获得知识、形成对社区的认识,并为成长为贡献者奠定基础。
3. 组织环境、人才流失与能力形成
某些话语声称,不合理的软件组织中所有有能力者都会离开,只剩无能者;任职时间越长,发展的不是设计能力而是内部政治技巧。这一叙述混合了值得检验的组织假设与缺乏依据的全行业等级判断。
- 离职与留任是条件性选择过程:恶劣环境可能与离职意愿相关,但谁离开、谁留下,还受满意度、劳动力市场选择、报酬、个人约束、组织关系和职业前景共同影响。2026 年对 224 名软件专业人员的横断面调查发现,工作满意度和组织嵌入性与离职意愿负相关,组织公平是嵌入性的重要预测因素。但它不能证明实际离职因果或留任者智力。61
- 心理安全是学习环境的一部分:惩罚提问、问题报告和错误的团队可能隐藏缺陷和坏消息。Edmondson 对 51 个团队的研究发现,心理安全与学习行为相关,学习行为又与团队绩效相连。62 这说明环境能促进或抑制学习,而不是证明留在特定组织的人天生无能。
- 激励会改变技术行为:若只奖励短期功能数量和按期完成,把设计审查、测试、文档和重构仅当成本,成员会优先可见的短期产出而非长期质量。SEI 技术债研究建议使债务可见、保护偿还工作免受新功能压力,并明确分配资源。63 反复质量问题应同时调查组织目标与资源分配,而不是只归于个人智力。
- 任职年限与专业能力不自动相同,但也并非无关:年限不能保证设计能力。缺少反馈、审查、事故分析、多样任务和学习时间时,经验可能固化为狭窄惯例。反之,长期任职者可能积累系统隐含规范、事故历史和跨组织依赖,把这些全部还原为政治技巧同样错误。利益协调和资源谈判可能是大型系统所需的协调能力,应与信息隐瞒、甩锅和派系竞争区分。
- 生产率与能力是多维的:SPACE 框架认为开发者生产率不能以单一活动量衡量,应结合满意度与福祉、绩效、活动、沟通协作、效率与流动。64 短期产出高但伴随倦怠、知识集中、大量返工和离职时,长期绩效可能脆弱。
- 组织与个人责任都要评价:雇主有责任检查工作量、权责对应、晋升和奖励标准、归罪文化、维护预算和学习机会。同时,环境解释并不会消除个人对骚扰、不负责任或能力发展的责任。把两种责任做成二选一,会同时削弱原因分析与改进。
4. 评价信息不对称与代理指标扭曲
软件预防效果、可维护性、设计质量和长期风险难以即时观察。评价者越难直接检查这些属性,就越可能把加班时长、代码量、紧急响应、艰深术语和复杂结构等可见信号当作真实绩效的代理。代理可以参考,但若作为目标奖励,容易测量的行为可能挤掉原本要测量的质量。
- 预防的不可见性与事故英雄主义:预防事故的工作表现为“事件没有发生”,不易看见;严重事故后的长时间恢复则十分可见。快速恢复应得到合理评价,但若只奖励恢复过程的牺牲而非预防、自动化、可观测性和后续行动,就难以结构性减少重复事故和过劳。本书把戏剧化恢复压倒预防、垄断评价的现象称为事故英雄主义。Google SRE 事后分析指南也强调找出贡献原因与防复发行动,而非个人归罪,并建议奖励事后分析和预防本身。65
- 混淆活动量与成果:工时、提交数、代码行和工单量显示部分活动,却不等于稳定性、用户价值和可维护性。用少量代码消除问题或自动化重复工作,可能反而降低活动量。因此把单一活动指标当生产率,会使简化和预防处于不利地位。SPACE 拒绝单一指标,正因为生产率包含满意度、成果、活动、协作和流动。64
- 炫示复杂性与可读性:术语、缩写、抽象和复杂结构有时必要,但本身不是专业性的证据。艰深对非技术评价者可能看似深度的假设可以检验;实际判断应依据代码审查、变更成本、缺陷率和可理解性。Buse 与 Weimer 根据 120 人的可读性判断建立指标,并报告与代码变更和缺陷测量相关。66 这说明可读性是可审查质量属性,而非证明某种文体在所有系统中导致缺陷。
- 评价者与开发者标准不一致:开发者与管理者对生产率和质量的定义可能不同。Storey 等的调查发现两组定义并不完全一致,开发者也不能准确预测管理者观点。67 这不证明非技术管理者总是错误,而提示不能把标准留为隐含,应明确协商角色、质量目标和时间范围。
- 验证评价制度:评价应结合多个时期和资料,而非个人印象。需要共同观察事故率与复发率、变更失败率、恢复时间、后续行动完成率、审查结果、技术债、用户影响和团队可持续性。Google 内部面板研究也把代码质量、技术债、工具支持、沟通、目标和组织过程与感知生产率相连,并报告代码质量改善往往先于生产率改善。68 但一个组织的结果不能原样普遍化到所有公司。
5. 样本、国家泛化与污名化类比
个人在组织和开发者中的经历可以成为发现问题的起点,却不是评价一个国家所有程序员的样本。
- 区分观察范围与总体:网络帖子、少数公司或个人职业经历,不能估计所有韩国开发者的智力或技术水平。招募路径、职位、经验、企业规模和产业未知的样本不保证代表性。
- 把国籍变成原因的跳跃:评价制度或发包结构可能扭曲质量,与“现象源于某国成员的本质能力”是不同主张。没有跨国比较数据和制度差异控制时,国籍只是分类标签,不是解释变量。
- 区分服务工作与技术能力:解释需求并与客户、运维和管理者协调,是软件工程的一部分。可以批评只迎合利益相关者的组织行为,但工作含服务因素并不使开发工作技术上更低等。
- 污名化类比的无关性:贬低残障者或嘲笑某群体为照护对象的比喻,无法解释评价制度如何运作,反而把可验证的组织批评转化为对人的污名,并把侮辱转嫁给与分析无关的群体。
- 跳跃到 Rust 结论:即使存在错误的组织评价,也不能单独证明批评 Rust 的原因、Rust 的普遍优越性或非使用者的低智力。组织激励假设和语言选择的工程有效性必须分别验证。
组织假设应以资料而非侮辱验证。大型组织需要按团队、职类和任职区间长期观察自愿离职率与原因、任职时间、入职时间、关键知识集中度、预防工作投入、非工作时间事故负担、复发率、后续行动完成、返工与缺陷外泄、技术债处理、心理安全和晋升公平。不能从个别案例或不满判断整个行业或国家的认知能力。
总之,接受批评、共享知识、奖励预防和长期质量的环境,有助于技术生态和组织的可持续性。反之,把结构问题归于个人智力或国籍,把显眼的辛苦和艰深自动换算为专业性,或把组织成员整体规定为低等群体,会阻碍可验证的原因分析。
10. 结论:生态可持续性的课题与展望
第10章提出 Rust 生态面临的课题,并综合本书的讨论。
首先,10.1节分析生态的质性成熟与向产业领域扩张所需的技术和政策课题。随后,10.2节在工程语境下重新界定 Rust 所强调的“安全性”和“性能”,提出技术选择的分析框架,并以此结束全书。
10.1 生态结构性改进的课题
Rust 若要扩展为通用系统编程语言,除语言的技术特性外,还需要整个生态在质量上进一步成熟。本节分析未来可能影响 Rust 生态的技术与政策课题。
1. 技术课题:ABI 稳定性与设计哲学的权衡
Rust 目前没有为标准库(libstd)提供稳定的应用二进制接口(ABI),多数程序采用静态链接。这是二进制体积增大的原因之一,也限制了其向资源受限系统扩展。
这种设计使语言与库能够持续改进和优化,但缺少动态链接会限制与其他语言的集成,以及作为系统库使用的可能性。因此,是否稳定 libstd 的 ABI,将成为 Rust 项目必须在“演进”和“兼容性”两种价值之间选择的技术议题。
2. 生态课题:确保库的稳定性与可靠性
以 crates.io 为中心的 Rust 库生态在数量上已显著增长,但质量方面仍有改进空间。许多核心库长期停留在 1.0 以下,意味着 API 可能不稳定;而依赖少数个人贡献的维护模式,也会给长期可靠性带来潜在风险。
其他开源生态为处理这些问题,采用了如下措施。
- 为核心库提供资金与人力支持: 通过基金会或企业赞助支持关键项目的维护。
- 引入成熟度模型: 建立评价库稳定性、文档水平和维护状态的等级体系,帮助用户选择依赖。
这些制度性机制可以帮助 Rust 生态走向质性成熟。
3. 扩展性课题:面向产业应用的灵活性
Rust 若要扩大应用领域,需要提高语言与生态的灵活性。
- 语言与工具的可用性: 如改变借用检查器分析方式的 Polonius 项目,涉及认知成本和生产率的工作会影响语言的可接近性。
- 执行模型的选择: Rust 当前的
async模型建立在零成本抽象上。若能选择性提供类似 Go goroutine 的轻量线程或绿色线程模型,可能成为网络服务领域是否采用 Rust 的一个变量。 - 生态扩展: 桌面 GUI、数据科学等领域的库开发,以及外部函数接口(FFI)技术,都会影响 Rust 的适用范围。
这些课题正由 Rust 社区与各工作组讨论,其结果将影响 Rust 的未来地位。
10.2 综合:同时处理技术、变更策略、能力、测量与组织环境的框架
本书分析了 Rust 语言的特性与相关话语,并通过与其他技术替代方案的比较描述了工程权衡。一个重要结论是:必须区分但同时审视语言特性、改变既有系统的方法、评价开发者能力的方法、观察这些能力的测量体系,以及能力得以发挥的组织环境。
“安全性”“性能”“改进”“能力”“测量”“组织”的含义
- 扩展安全性的含义: 编译时内存安全保障是 Rust 的重要功能。但软件系统的可靠性还包括程序的逻辑正确性、错误发生时维持服务的韧性、部署与回滚能力,以及共同体的协作环境。
- 扩展性能的含义: Rust 的设计重视运行时性能优化。项目效率还包括开发生产率、包含编译时间在内的反馈循环速度、故障恢复时间与维护成本。
- 扩展改进的含义: 改进不等于更换语言。重构改善结构与可变更性;分析与测试提高缺陷发现能力;部分替换把强保障集中到特定风险;全面重写则同时重新设计语言与架构。每种策略都有不同收益与失败方式。
- 扩展能力的含义: 熟练掌握某种语言可以是重要职业能力,但不等同于一般智力、完整的软件工程能力或人的价值。能力评价应围绕岗位所需知识与实际表现来设计。
- 扩展测量的含义: 必须区分容易观察的活动与真实成果。单一指标不能代表整体质量,还应审视指标与奖励绑定后会诱导何种行为。
- 扩展组织的含义: 个人表现会受到工具、工作量、权限、反馈、奖励、心理安全和知识分布结构的影响。不能把组织问题全部归结为个人无能,也不能以环境为理由抹去个人责任。
技术选择与变更策略的分析框架
- 问题领域(Problem Domain): 待解决问题的要求是什么?延迟、吞吐量、硬件控制、开发速度、韧性与形式化证明之中,何者优先?
- 缺陷模型与基线(Defect Model and Baseline): 实际故障与漏洞主要属于哪一类?内存生命周期、数据竞争、逻辑错误、运维失误中何者占主导?是否测量了当前的缺陷率、故障影响、修复时间与性能?
- 所需保障水平(Required Assurance): 开发者纪律与分析工具是否足够,是否需要编译器强制,或是否需要 SPARK 一类形式化验证?所有组件是否都需要相同等级的保障?
- 变更策略(Change Strategy): 同语言重构、现代化、选择性替换高风险模块、新组件使用 Rust,或全面重写,哪一种范围与问题规模相称?
- 决策时间范围(Decision Horizon): 是否区分了已经支出且无法收回的成本,与未来的维护、迁移、并行运营、中断及机会成本?是否把“年代久远”本身作为独立的废弃理由?
- 全生命周期成本(Lifecycle Cost): 除实现成本外,是否包括培训、双语言维护、FFI、构建、调试、部署、运行、回滚和长期人才供给?
- 生态与长期稳定性(Ecosystem and Longevity): 必需库和工具的稳定性、标准化、供应商支持、安全响应与持续维护能力,是否符合项目寿命?
- 失败场景与透明度(Failure and Transparency): 新策略失败时能够回滚到什么范围?是否用讨论优势的同一标准讨论限制与失败案例?
- 评价对象与指标的对应(Construct and Measure): 是否把语言技术特性翻译成开发者的智力或人格?招聘标准衡量的是实际工作行为和成果,还是某一技术共同体的身份?
- 组织环境与激励(Organization and Incentives): 日程、晋升、奖励与责任结构是否鼓励长期质量、知识共享和问题报告?是否把高流动率、倦怠、知识集中和技术债仅仅视为个人问题?
- 可证伪性与动机归因(Falsifiability and Motive Attribution): 什么结果会促使当前判断改变?是否不回应反对意见的内容,而通过推测批评者的无知、恐惧或自卑来保护结论?
- 代理指标与激励效果(Proxy Measures and Incentives): 是否以工作时长、代码量、紧急响应和艰深表达作为成果的代理指标?这些指标是否会使预防、简化与知识共享处于不利地位?
- 样本与泛化范围(Sample and Generalization): 所观察的团队、公司或在线群体代表什么总体?是否把有限经验扩大为整个行业、国家或人类群体的特征?
- 案例归因与来源可追溯性(Attribution and Traceability): 观察到的改进来自语言、重新设计、架构、硬件还是团队熟练度?是否从原始来源核实数字、CVE、调查年份和路线图状态,并区分估计值与观察值?
基于证据的变更程序
技术选择应更接近比较实验,而不是宣言。
- 用回归测试与可观测指标固定既有行为。
- 找出实际缺陷与成本集中的边界。
- 在代表性模块中分别试验 C/C++ 内部改进与选择性 Rust 替换。
- 在相同期间和条件下比较缺陷发现率、运行时性能、开发时间、运维复杂度、二进制与内存使用量及可维护性。
- 记录随语言更换一同改变的数据结构、架构、运行时、硬件与运维政策,以区分语言效应与重新设计效应。
- 把数字、调查规模、CVE 描述与路线图状态追溯到原始来源,并区分观察值、估计值、发布前发现和自报资料。
- 同时测量团队工作量、审查结构、处理技术债的能力、故障响应负担、流动率与知识集中度,以区分技术效应和组织效应。
- 分别记录预防工作与故障响应,并通过故障复发率和事后行动完成率,同时评价恢复的可见性与长期改进。
- 定期检查评价指标是否诱导增加代码量、不必要的复杂性、过劳或隐瞒问题等反功能行为。
- 分别把政府建议、研究计划和特定企业的成功案例理解为政策方向、研究目标和有条件的实证资料。
- 只有在结果达到目标时扩大适用范围,否则停止或回滚。
在这个框架中,中心问题不是“哪种语言绝对优越”“哪种语言的使用者更聪明”或“哪个国家的开发者本质上更低等”,而是“以何种保障和成本减少何种缺陷”“观察到的成果中有多少来自语言本身”“以何种证据评价何种岗位能力”“测量指标会诱导何种行为”“何种组织条件促进学习与长期质量”,以及“什么证据会促使我们改变判断”。重构与重写是可以根据问题规模和证据组合使用的工程手段;语言熟练度和组织环境则是必须按岗位与语境分别评价的不同变量。把反对意见重新解释为对方缺陷、从而阻止证伪的主张,不能用于这个框架下的技术比较。
后记
本书在历史与工程语境中分析了 Rust 语言的技术特性及相关话语。分析表明,Rust 在 Safe Rust 范围内提供编译时内存安全保障,这是系统编程中的一项重要工程成就。
Rust 的设计原则——所有权模型、零成本抽象和通过类型系统处理错误——是对 C++ RAII、Ada/SPARK 安全模型及函数式编程等既有思想的整合与强制实施;与此同时,也伴随着学习曲线、编译时间、二进制体积和特定设计模式实现复杂度等工程权衡。
既有系统的选择并非“放任 C/C++ 不管”与“全部用 Rust 重写”之间的二选一。同语言重构可以改善结构、可验证性、缺陷率与维护成本;选择性或全面引入 Rust,则可以为特定缺陷类型提供更强的默认保障。重构和重写不是同义词,任何一方的价值也不会因为另一方的存在而归零。
学习 Rust 是掌握新概念和工具的有价值活动,但学习成果不等于一般智力的证明,也不意味着其他开发者低人一等。开发者能力是语言熟练度、设计、验证、运维、协作与领域知识共同作用的结果。
在软件组织的评价中,显眼的加班、代码量、紧急响应与艰深表达并不是质量的自动证据。预防、简化、可读性、防止复发和可持续协作等不那么戏剧化的成果,也必须纳入测量。同样,不能把在少数组织中观察到的评价失败,扩大成对某一国家全部开发者智力或人格的判断。
此外,当技术共同体内部形成强调技术优越性的叙事时,它会影响对技术的评价,以及与其他生态的互动。这是本书所分析的部分话语中的特征,而非整个社区的属性。类似模式也见于过去的操作系统竞争,可被解释为技术选择与群体身份相连时出现的社会动力。
归根结底,本书的目的不是支持或排斥某项特定技术,而是把技术保障范围、变更成本、失败方式与话语结构分开审视。开发者与技术共同体可以依据问题性质和可测量结果,而非口号,组合使用重构、现代化、部分替换与重写。
附录:技术讨论中观察到的论证谬误案例分析
本附录为解释正文讨论的沟通方式,分析在线技术讨论中出现的论证模式类型。所列案例用于说明论证谬误,均已匿名化;目的在于分析其论证结构及其对讨论的影响。
案例1:人身攻击谬误(ad hominem)
- 语境: 一名开发者指出 Rust 的学习曲线和
async复杂度对生产率的影响时,部分用户除了技术问题外,还倾向于针对发言者作出回应。 - 观察到的回应: “坦白说,你不理解
async不是 Rust 的问题,而是你的能力问题。你大概还没有准备好处理复杂系统,考虑回到更简单的语言吧。” - 分析: 该回应没有讨论所提出的技术批评——学习曲线和
async复杂度——而是评论提出主张者的能力与资质。这属于偏离议题、攻击对方的人身攻击谬误,会干扰技术讨论。 - 社会—技术原因分析: 这类回应可能与 Rust 社区围绕“安全性”形成的身份认同相连。当内存安全不只是技术功能,而被视为 Rust 的价值或哲学时,对
async或借用检查器等安全实现机制的批评,可能被理解为对技术本身的挑战。讨论于是从“这一功能有什么问题?”转向“你为什么不能理解这一功能?”,把问题从技术转移到个人能力,为人身攻击创造环境。
案例2:发生学谬误(genetic fallacy)与情境性谬误
- 语境: Rust 的借用检查器(borrow checker)在编译时检查内存访问规则,以防止数据竞争等错误。一名 C++ 用户指出,借用检查器在某些情况下可能限制开发者的灵活性。对此,部分回应倾向于谈论主张者的背景或动机,而不是主张内容。
- 观察到的回应: “你觉得 Rust 的规则是‘限制’,只是因为几十年来习惯了 C++ 的‘不安全’方式,对新范式产生了抵触。这是对旧方式依恋造成的偏见。”
- 分析: 该回应没有反驳主张内容,而是质疑提出主张的动机与背景——熟悉 C++。这可视为一种发生学谬误:根据主张来源或假定动机评价主张,并把技术问题转化为心理分析。
- 社会—技术原因分析: 这种谬误建立在 Rust 话语中的“C++ 替代者叙事”之上。在该叙事中,C++ 常被规定为“不安全的过去”。因此,拥有 C++ 背景者的批评可能不论内容为何,都被视为依恋“旧方式”的观点。这形成了通过攻击来源而不是研究技术实质来驳回主张的环境。
案例3:稻草人谬误(straw man fallacy)
- 语境: 一篇博客文章比较分析 Rust 的
Result类型与 Java 的检查异常时,部分用户把原主张变形后加以攻击。 - 观察到的回应: “所以你的主张是 Rust 的错误处理毫无用处吗?你完全不懂
panic和Result如何解决空指针问题。你只是想懒惰地把所有东西包在try...catch里。” - 分析: 回应把原来的比较分析——“相较之下存在不足”——改成“毫无用处”,然后攻击这个被改造的主张。这不是回应对方的真实立场,而是攻击一个更容易击倒的替代物,属于稻草人谬误。
案例4:范畴错误、虚假两难与涅槃谬误
- 语境: 在讨论如何改进既有 C/C++ 系统时,同语言内部重构与 Rust 重写被拿来比较。
- 观察到的回应: “C/C++ 的重构就是用 Rust 重写,除此之外的重构毫无意义。”
- 分析1——范畴错误(category mistake): 把保持外部行为、改善内部结构的重构,与用新实现替换旧实现的重写当成同一种工作。把不同变更类别塞进同一术语,预先设定了结论。
- 分析2——虚假两难(false dilemma): 只保留维持现状和全面重写两种选项,排除加强静态分析、建立测试、隔离危险代码、采用现代 C++、选择性 Rust 替换等中间策略。
- 分析3——涅槃谬误(nirvana fallacy): 因 C/C++ 重构不能在语言层面消除所有内存错误,就把降低缺陷概率和维护成本的部分效果也视为毫无价值;它用完美理想否定现实改进。
- 分析4——单一指标还原: 把软件质量还原成内存安全,排除逻辑正确性、可用性、兼容性、经验证行为、运维风险与迁移成本。
- 话语功能: 该主张不是要求具体缺陷数据和成本比较,而是把某一语言选择定义为“唯一有意义的改进”。技术选择因而可能从可检验假设变成身份或宣言。
案例5:遗留系统的道德化、错误类比与人身攻击
- 语境: 在讨论既有系统的维护、现代化或替换时,一种主张把遗留系统的存在本身规定为负面事物。
- 观察到的回应: “说遗留系统不坏,就像说有害物质不坏;选择 Rust 以外后端技术的人是不正常的。”
- 分析1——错误类比(false analogy): 把具有生理危害的物质,与描述系统历史及组织状态的“遗留系统”放进同一评价范畴。两者相关属性不同,因此比较不能支持结论。
- 分析2——道德化与年龄偏见: 不测量支持状态、缺陷率、维护成本和迁移风险,便把年代久远直接转化为恶或废弃理由。
- 分析3——沉没成本的误用: 仅因过去支出而继续维持可能是错误的,但未来迁移成本和功能回归风险不是沉没成本,不能据此排除。
- 分析4——人身攻击与虚假两难: 不提供技术依据,而攻击作出其他选择者的能力或正常性,只留下采用某种语言或“不理性”两种选项。
- 分析5——事实错误: 把 JSP 与 PHP 归类为在浏览器中执行的前端技术,但两者通常都在服务器端处理请求并生成响应。
- 话语功能: 它不审视系统实际质量和迁移条件,而是把技术选择变成道德与智力资质标准,将不同选择排除出讨论。
案例6:把语言熟练度等同于智力,并还原为单一指标
- 语境: 在批评开发者招聘评价项目过多且形式化的同时,一种主张提出应把特定语言熟练度作为更重要的标准。
- 观察到的回应: “多个评价标准都没有意义,重要的是学习 Rust 并提高智力。”
- 分析1——范畴错误(category mistake): Rust 知识和使用经验是后天学习的领域熟练度。把它等同于一般智力或整体开发能力,会把不同心理与职业构念当成同一个构念。
- 分析2——因果跳跃: 即便在 Rust 学习者中观察到某些能力,也必须区分是 Rust 学习造成,还是先验知识、教育机会、兴趣和自我选择的结果。
- 分析3——单一指标还原: 把软件开发绩效还原为一种语言熟练度,排除需求分析、设计、调试、测试、运维、安全、协作和领域知识。
- 分析4——自相矛盾: 一方面批评过多标准损害行业,另一方面用未经职业效度验证的单一标准评价所有开发者。问题不在标准数量,而在工作相关性与预测效度。
- 条件成立时合理的部分: 若岗位要求立即处理 Rust 代码库,评价 Rust 熟练度是合理的。但结果是对该岗位能力的证据,而非一般智力或人类优劣的证据。
- 话语功能: 把学习某项技术描绘为智力身份提升,并把非使用者规定为低等群体,从而把可验证的技术选择和招聘标准讨论转化为身份竞争。
案例7:组织选择假设的过度泛化与群体侮辱
- 语境: 一种主张批评软件组织不合理的工作结构与内部政治对人才保留和能力成长的影响。
- 观察到的回应: “软件行业大多数人智力低下,连某种语言也学不会;正常人都离开了,只剩无能者,有经验的人学会的不是设计而是政治。”
- 分析1——可检验的结构性假设: 恶劣工作条件、不公平评价、过度负荷和惩罚问题提出,可能妨碍留才与学习,这可以经验检验。要求雇主不要只把招聘失败或质量问题归咎个人,而应检查环境,也在一定条件下合理。
- 分析2——轻率泛化与武断的选择效应: 由少数组织经验或在线案例推断整个行业中有能力者都会离开,只剩无能力者。实际上,工作满意度、报酬、替代选择、组织嵌入性和个人条件都会影响去留。
- 分析3——测量对象替换: 把离职和留任这种组织行为转化为智力证据,再以特定语言熟练度充当智力代理。两个步骤都没有直接测量,因此结论只是重复前提。
- 分析4——单一原因论: 不区分资深员工的设计能力、默会知识、协调能力与政治行为,把全部经验积累还原为权力斗争。组织政治可能存在,但不能因此否定专业成长。
- 分析5——责任的二选一: 组织环境责任与个人专业成长和行为责任可以同时成立。只承认一方会遗漏可改进原因。
- 话语功能: 它虽然提出结构问题,却把全体从业者规定为智力和道德上的低等群体,把可检验的组织分析变成侮辱与技术身份排序。
案例8:自我封闭论证与对反对意见的动机推测
- 语境: 在讨论某种语言的优点和学习价值时,一种主张用批评者智力低下和自卑来解释其批评。
- 观察到的回应: “正常程序员会理解 Rust 的优越性和学习价值。贬低或拒绝 Rust 是因为智力低下;听到 Rust 很好就反感,是因为自己的自卑暴露了。”
- 分析1——循环的资格定义: 先把赞同 Rust 者定义为正常,把反对者定义为低等,再把该分类作为 Rust 优越性和智力差异的证据。结论已经包含在前提中。
- 分析2——发生学谬误与动机归因: 不反驳有关性能、生产率、生态和适用条件的批评,只通过推测其源于自卑来降低主张价值。发言动机不能代替对内容效度的判断。
- 分析3——自我封闭性: 同意成为支持主张的证据,反对成为低等的证据,不快和否认也成为自卑或防御的证据。没有任何观察能够反驳主张,因此它不能作为技术假设接受检验。
- 分析4——删除条件判断: Rust 的优点与学习价值会随项目缺陷模型、性能要求、生态、人力和迁移成本而变化。删除这些条件,把唯一结论规定为正常,会把工程选择变成智力等级。
- 话语功能: 它把关于技术利弊的所有反论都吸收到对方的心理缺陷中,以保护既有信念,并按赞同与否分配讨论资格。
案例9:代理指标误用、国家层面的泛化与污名化类比
- 语境: 对非技术评价者无法正确判断软件开发者成果、从而降低行业技术水平的组织批评,被扩大成关于某国开发者智力及其对 Rust 态度的主张。
- 观察到的回应: “加班修复问题的人比预防问题的人更受认可,艰深缩写和复杂代码比可读代码更显得专业。这种评价结构让韩国程序员变成低智力群体,只会迎合客户情绪而不解决技术问题。”
- 分析1——可检验的评价假设: 有些组织可能更奖励可见恢复而非预防,或更奖励易观察的活动与外观而非真实质量。可通过评价标准、故障复发、后续行动和质量资料检验这一假设。
- 分析2——故障英雄主义与结果偏差: 恢复者的能力与努力应受承认,但事故发生会使恢复可见,而预防不可见。长期评价应把预防效果、重复事故与事后行动和响应速度一起考虑。
- 分析3——代理指标与复杂性炫示: 加班、代码量、艰深术语和晦涩结构都不是专业性的充分条件。若奖励这些信号而非成果,成员会优化指标,牺牲可维护性与简洁性。
- 分析4——国家层面的轻率泛化: 一个人接触的少数公司和在线群体不能代表全部韩国程序员。在没有比较样本和制度变量控制的情况下,不能得出整个国家智力或技术水平的结论。
- 分析5——跳跃到 Rust 结论: 扭曲的评价制度存在,并不能证明 Rust 批评的原因或 Rust 的普遍优越性。组织评价和语言选择是需要不同资料与比较标准的独立主张。
- 分析6——服务工作的范畴错误: 协调客户和利益相关者的需求是软件工程的一部分。可以批评需求扭曲或过度情绪劳动,但不能把服务要素本身等同于技术无能。
- 分析7——污名化类比: 贬低残障者或嘲弄照护关系的类比不能解释组织激励,只会把污名转嫁给与分析无关的群体。这种表达削弱结构批评的可验证性与说服力。
- 话语功能: 它先提出值得检验的评价制度问题,再扩大为国籍、智力和语言身份的等级,把组织分析转化为群体侮辱的根据。
案例10:累积产业成功案例、因果归因与“标准”宣告
- 语境: 一篇依据内存安全、产业采用和政府建议来鼓励学习与采用 Rust 的文章,连续列举了 Android、Discord、Cloudflare、AWS、Linux 内核、漏洞与生态规模。
- 观察到的回应: “Android 的漏洞比例和 Rust 漏洞密度、Discord 与 Pingora 的性能、Firecracker 启动时间、Linux 内核采用以及政府建议,都指向同一方向。问题已不是是否使用 Rust,而是还要信任不可验证代码多久;现在不使用 Rust 的一方才必须为例外辩护。”
- 分析1——在一定条件下很强的证据: 在新建高风险系统代码中,Rust 确实有助于内存安全、延迟可预测性、资源效率与开发稳定性。把它缩小为流行或玩具语言,同样不符合资料。
- 分析2——混合成果归因: 服务重写不仅改变语言,还可能改变数据结构、连接池、多线程架构、缓存大小与运维方式。把全部前后差异归为语言效果,就无法分离架构与实现效果。
- 分析3——合并不同分母: Android 漏洞比例、以发布前潜在漏洞为基础的代码密度、特定服务的运维指标和开发者人数估计,具有不同总体与测量方法。多个数字方向相同,并不会自动形成一个普遍效应量。
- 分析4——来源的精度漂白: 把原文没有的 P99 降幅、来自另一项调查的开发者人数、没有 Rust 归属的 CVE 或尚未确定的路线图,与具体数字和年份一起陈述,可能显得精确,但与原始来源不一致的精度不会增强证据。
- 分析5——
unsafe的双面意义: 把unsafe标在较小区域并集中审计是有效优势。但错误的安全契约可能经由安全 API 和共享状态传播,因此审计范围必须包括不变式与调用边界,而不是只计算花括号内的代码行。 - 分析6——区分政策方向与单一语言义务: 政府机构建议采用内存安全语言,以及 DARPA 推进 C-to-Rust 研究,都是重要方向信号。但要把涉及多种内存安全语言的建议或研究目标,转化为所有系统都必须使用 Rust,还需要额外论证。
- 分析7——技术默认值的层次: 在高风险新系统代码中把内存安全设为默认要求、在特定领域优先审视 Rust、把 Rust 设为所有既有系统的规范性默认值,是三个不同命题。必须分别处理其证明责任与例外条件。
- 话语功能: 广泛列举真实而强劲的产业成果,却消除每个案例的条件和来源差异,会使有条件的技术选择看起来像历史上已经确定的唯一标准。这种方式提高了反对者的举证负担,却不审视普遍化所需的额外前提。
-
一种由运行时跟踪对象可达性等状态,并自动回收不再使用的内存的内存管理技术。 ↩
-
Rust Core Team, Laying the foundation for Rust’s future; Aaron Turon, Abstraction without overhead: traits in Rust。前者说明 Rust 作为 Mozilla Research 项目起步并转为独立项目的历史,后者说明内存安全、数据竞争防止与抽象成本这几个设计轴。 ↩
-
The Rust Reference, Behavior considered undefined 与 Behavior not considered unsafe; The Rustonomicon, How Safe and Unsafe Interact。官方文档也明确指出
unsafe契约的健全性责任、语义规则尚不完整,以及死锁、资源泄漏和逻辑错误与内存不安全之间的区别。 ↩ ↩2 -
Aaron Turon, Abstraction without overhead: traits in Rust。该文把零成本抽象说明为 Rust 设计的核心原则,但并不普遍保证某个特定程序的性能结果。 ↩
-
The Rustonomicon, Data Races and Race Conditions; The Rust Programming Language, Using Threads to Run Code Simultaneously。官方文档区分 Safe Rust 对数据竞争的防止,以及一般竞态条件与死锁属于不同问题。 ↩
-
The Rust Programming Language, Understanding Ownership、References and Borrowing 与 Validating References with Lifetimes。官方文档区分所有权、访问权限与引用有效性的作用,并说明生命周期标注不会改变引用实际存活的时间。 ↩ ↩2 ↩3
-
Rust Project Goals, Stabilize and model Polonius Alpha 与 The Borrow Checker Within。2026 年官方目标说明,将使当前 NLL 分析拒绝的条件借用与 lending iterator 得到接受,而需要完全流敏感性的一些模式仍留作后续工作。 ↩
-
Standard C++ Foundation, What is the zero-overhead principle?; Aaron Turon, Abstraction without overhead: traits in Rust。两份资料分别把该原则说明为 C++ 的设计原则以及 Rust 所继承的抽象目标,但并不规定特定程序的普遍性能结果。 ↩ ↩2
-
Rust Compiler Development Guide, Monomorphization; The Cargo Book, Profiles; The rustc book, Codegen Options; Richard Uhlig et al., Instruction Fetching: Coping with Code Bloat。这些资料说明单态化的编译时间与二进制体积成本、优化与代码生成单元及 LTO 的权衡,以及代码规模可能对指令获取造成的影响。 ↩ ↩2 ↩3
-
The Rust Reference, Trait object types; Rust 标准库关键字文档,
dyn; Rust 标准库,Box<T>、Vec<T>与String; The Rust Reference, Panic。这些文档分别区分 trait object 的运行时分派、堆分配与重新分配,以及越界索引触发的panic。 ↩ ↩2 -
The Rust Programming Language, Performance in Loops vs. Iterators。官方示例只观察到一个工作负载中的相近性能,并明确指出更全面的比较需要不同的输入与条件。 ↩
-
The Rust Programming Language, Defining an Enum 与 Recoverable Errors with
Result; Rust 标准库,Option与Result; The Rust Reference, Pointer types。官方文档区分了Option与Result的状态表达、未使用Result的must_use警告,以及非空引用和可为空原始指针之间的边界。 ↩ ↩2 ↩3 -
The Rust Reference,
matchexpressions, Patterns, Thenon_exhaustiveattribute。这些文档说明模式守卫的条件行为、穷尽性检查、通配符以及外部非穷尽类型的 API 演进规则。 ↩ -
The Rust Reference, Behavior considered undefined。官方参考把构造无效枚举判别值、引用和类型值归类为未定义行为,并说明实现者必须在
unsafe与 FFI 边界保持有效性前提。 ↩ -
The Rust Reference, Type layout 与 Panic; Rust 标准库,
Optionrepresentation、Option::unwrap与Result::unwrap。这些资料区分默认枚举布局的有限保证、特定类型的空指针优化,以及unwrap系列的panic行为与 panic 策略。 ↩ ↩2 -
Ada 和 SPARK 使用形式化验证技术,可以在程序所有可能的执行路径上以数学方式证明特定属性,例如不存在运行时错误和逻辑正确性。它提供比 Rust 借用检查器的内存安全保证更广泛的保障层次,并已用于空中交通管制、核电站控制系统等要求特定安全与可靠性等级的领域。(参见 AdaCore 文档和 SPARK User’s Guide。) ↩
-
The Rustonomicon,“Meet Safe and Unsafe”。“When we say that code is Safe, we are making a promise: this code will not exhibit any Undefined Behavior.” https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html ↩
-
此功能主要用于处理与外部 C 库边界(FFI)上的异常,或者用于线程池等场景,避免某个线程失败导致整个系统中止。 ↩
-
Martin Fowler,Refactoring: Improving the Design of Existing Code,第 2 版,2018。该书把重构定义为在不改变可观察行为的情况下改善内部结构的受控变更。 ↩
-
C++ Core Guidelines 是由 Bjarne Stroustrup 和 Herb Sutter 发起的编码指南,涵盖所有权、资源管理、接口设计等领域,为 C++ 编程提供建议。多种静态分析工具支持自动检查这些规则。(参见 https://isocpp.github.io/CppCoreGuidelines/。) ↩
-
JetBrains,《The State of Developer Ecosystem 2023》,C++ 部分。报告显示,虽然 C++17 和 C++20 已广泛使用,但仍有相当多项目使用早于 C++11 的标准。 ↩
-
Rust async 模型的复杂性在项目内部也被视为改进课题。例如 Jon Gjengset 在其 Crust of Rust 系列演讲“The Why, What, and How of Pinning in Rust”中专门详细解释
Pin。核心开发者 Niko Matsakis 也多次在自己的博客讨论相关愿景与改进方向。需要如此大量解释,本身说明这些概念仍是 Rust 社区的学习门槛。 ↩ -
Matthew Prince,“2025 年 11 月 18 日 Cloudflare 服务中断”,Cloudflare Blog,2025-11-18。https://blog.cloudflare.com/ko-kr/18-november-2025-outage/ ↩
-
软件包大小引用 Alpine Linux v3.22 稳定版官方软件包数据库中的“Installed size”。该表并非比较某一时点的最新性能,而是展示不同语言生态设计方式对二进制大小产生的结构性倾向。稳定版内的小幅补丁或版本变化不会显著改变这一根本趋势,因此为保证可复现性与论证一致性,采用一个特定稳定版。各软件包版本如表中所列。 ↩
-
解压
linux-6.15.5.tar.xz后,在源代码根目录不加额外选项执行cloc .得到结果。提供这些信息是为了让读者能够按相同方法复现分析。 ↩ -
CMU Software Engineering Institute,“The Growing Importance of Sustaining Software for the DoD: Part 1”。文章说明软件不会物理磨损,但因硬件与运行环境老化、需求变化、缺陷与性能问题而需要持续维护。https://sei.cmu.edu/blog/the-growing-importance-of-sustaining-software-for-the-dod-part-1/ ↩
-
Robert C. Seacord 等,“Legacy System Modernization Strategies”,CMU/SEI-2001-TR-025。报告考虑大型遗留系统的规模、复杂性与脆弱性,比较包括渐进现代化在内的多种替代方案。https://www.sei.cmu.edu/library/legacy-system-modernization-strategies/ ↩
-
本文使用的“银弹叙事(silver bullet narrative)”是技术社会学中的分析术语,并非意在贬低特定技术或社区。它指面对复杂问题时,相信存在一个被简化的单一技术解决方案的倾向,也与“技术胜利主义(technological triumphalism)”相联系。本文使用该术语是为了描述所分析话语的结构。 ↩
-
第四部分的话语分析不针对特定个人或非公开社区。其依据是对任何人都能访问的公开信息中反复出现的论证模式所作的定性观察,包括 X(原 Twitter)、Hacker News、Reddit(如 r/rust、r/programming)等平台上的公开讨论,大量以“Why Rust?”为主题的技术博客,以及相关技术会议演讲的问答环节。分析目的不是测量统计频率,而是理解这些话语的结构与逻辑。 ↩
-
Oracle, “JavaServer Pages Technology”;PHP Documentation Group, “What is PHP and what can it do?”。JSP 使用服务器端对象构造响应,PHP 官方文档说明 PHP 代码在服务器执行,并把结果发送给客户端。https://docs.oracle.com/javaee/5/tutorial/doc/bnagx.html,https://www.php.net/manual/en/intro-whatis.php ↩
-
“M$”是 1990 年代部分 Linux 与开源社区用来批评 Microsoft 商业政策的表达。它把 Microsoft 中的“S”换成象征金钱的美元符号(M$、Micro$oft),以表达对其商业主义的批评。 ↩
-
RTFM 是“Read The Fucking Manual”的缩写,意为“去读那该死的手册”。它常被用来要求提出初级问题的用户自行寻找答案,体现了 1990 年代黑客文化中排他性的一面。 ↩
-
不依据主张内容,而依据其来源或动机评价其价值,属于“发生学谬误(genetic fallacy)”。参见附录“案例 2:发生学谬误”。 ↩
-
不讨论批评是否有效,而攻击提出主张者的能力或品质,属于“人身攻击谬误(ad hominem)”。参见附录“案例 1:人身攻击谬误”。 ↩
-
Antony Flew, Thinking about Thinking: Or, Do I Sincerely Want to Be Right?, Fontana/Collins, 1975。Flew 所说的“No-true-Scotsman Move”,是指不接受一般化命题的反例,而是事后改变“真正”成员的定义,将反例排除在外。 ↩
-
Ronny Scherer、Fazilat Siddiq、Bárbara Sánchez Viveros, “The Cognitive Benefits of Learning Computer Programming: A Meta-Analysis of Transfer Effects”, Journal of Educational Psychology, 111(5), 2019, pp. 764–792, DOI: 10.1037/edu0000314。研究报告总体迁移效应为
g = 0.49、近迁移效应为g = 0.75、远迁移效应为g = 0.47。但它没有检验 Rust 特有效果或一般智力上升,还必须同时考虑研究设计和对照组类型造成的差异。 ↩ -
Paul R. Sackett, Charlene Zhang, Christopher M. Berry, Filip Lievens, “Revisiting Meta-Analytic Estimates of Validity in Personnel Selection: Addressing Systematic Overcorrection for Restriction of Range”, Journal of Applied Psychology, 107(11), 2022, pp. 2040–2068, DOI: 10.1037/apl0000994。分析传统选拔工具绩效预测有效性估计可能存在系统性过度校正。 ↩ ↩2
-
Thomas Claburn, “Rust Foundation apologizes for bungled trademark policy”, The Register, April 17, 2023. https://www.theregister.com/2023/04/17/rust_foundation_apologizes_trademark_policy/ ↩
-
Rust Foundation, “Rust Trademark Policy Draft Revision & Next Steps,” Rust Foundation Blog, April 11, 2023. https://rustfoundation.org/media/rust-trademark-policy-draft-revision-next-steps/ ↩
-
National Security Agency, “Software Memory Safety,” CSI-001-22, November 2022. https://media.defense.gov/2022/Nov/10/2003112742/-1/-1/0/CSI_SOFTWARE_MEMORY_SAFETY.PDF ↩
-
Office of the National Cyber Director, “Back to the Building Blocks: A Path Toward Secure and Measurable Software,” February 2024. https://bidenwhitehouse.archives.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf ↩
-
Jeff Vander Stoep, Alex Rebert, “Eliminating Memory Safety Vulnerabilities at the Source,” Google Online Security Blog, September 25, 2024. 报告 Android 内存安全漏洞占比六年间从 76% 降至 24%,并说明优先安全编写新代码的策略。https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html ↩
-
Jeff Vander Stoep, “Rust in Android: move fast and fix things,” Google Online Security Blog, November 13, 2025. 提供 2025 年 Android 内存安全漏洞占比、约 500 万行 Rust 代码、基于一项发布前潜在漏洞的密度估计,以及代码审查和回滚资料。https://security.googleblog.com/2025/11/rust-in-android-move-fast-fix-things.html ↩
-
Jesse Howarth, “Why Discord is switching from Go to Rust,” Discord Blog, February 4, 2020. 说明特定 Read States 服务的缓存与 GC 条件及重写结果,并明确不建议重写所有系统。https://discord.com/blog/why-discord-is-switching-from-go-to-rust ↩
-
Yizhou Zhang, “How we built Pingora, the proxy that connects Cloudflare to the Internet,” Cloudflare Blog, September 19, 2022. 说明 CPU、内存下降的多重原因,包括连接复用、多线程结构和消除语言边界。https://blog.cloudflare.com/how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet/ ↩
-
Arun Gupta, “Announcing the Firecracker Open Source Technology: Secure and Fast microVM for Serverless Computing,” AWS Open Source Blog, November 26, 2018. 给出
i3.metal、默认 microVM 大小与最小设备模型下低于 125 毫秒的启动时间。https://aws.amazon.com/blogs/opensource/firecracker-open-source-secure-fast-microvm-serverless/ ↩ -
Miguel Ojeda, “[PATCH] rust: conclude the Rust experiment,” Linux Kernel Mailing List, December 13, 2025. 说明实验结束与持续支持,并指出未完成组合和剩余工作。https://lists.openwall.net/linux-kernel/2025/12/13/212 ↩
-
Ilia Afanasiev, “Is Rust the Future of Programming?”, JetBrains Blog, May 13, 2025. 根据 2024 Developer Ecosystem 数据估计过去十二个月 Rust 用户 226.7 万、主语言用户 70.9 万。https://blog.jetbrains.com/rust/2025/05/13/is-rust-the-future-of-programming/ ↩
-
Rust Survey Team, “2025 State of Rust Survey Results,” Rust Blog, March 2, 2026. 报告 7,156 份回答并说明样本外推限制。https://blog.rust-lang.org/2026/03/02/2025-State-Of-Rust-Survey-results/ ↩
-
National Vulnerability Database, “CVE-2025-30388.” 记录 Windows Win32K-GRFX 堆缓冲区溢出和本地攻击向量,未归属 Rust 实现。https://nvd.nist.gov/vuln/detail/CVE-2025-30388 ↩
-
Ubuntu Security, “CVE-2025-68260.” 说明 Rust Binder
unsafe列表删除操作与并行访问如何造成数据竞争和指针损坏。https://ubuntu.com/security/CVE-2025-68260 ↩ -
Rust Project Goals, “Just add async,” 2026. 说明同步与异步代码功能差距、同步析构限制及 2026—2027 年探索事项。https://rust-lang.github.io/rust-project-goals/2026/roadmap-just-add-async.html ↩
-
National Security Agency and Cybersecurity and Infrastructure Security Agency, “Memory Safe Languages: Reducing Vulnerabilities in Modern Software Development,” June 24, 2025. 建议采用多种内存安全语言以减少漏洞。https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/4223298/nsa-and-cisa-release-csi-highlighting-importance-of-memory-safe-languages-in-so/ ↩
-
Defense Advanced Research Projects Agency, “TRACTOR: Translating All C to Rust.” 说明自动把遗留 C 翻译为 Rust 的研究计划目标和评价结构。https://www.darpa.mil/research/programs/translating-all-c-to-rust ↩
-
Microsoft Security Response Center, “A Proactive Approach to More Secure Code”, 2019-07-16. https://msrc.microsoft.com/blog/2019/07/16/a-proactive-approach-to-more-secure-code/ ↩
-
Google 在多个项目中强调内存安全的重要性。
Chrome: “The Chromium project finds that around 70% of our serious security bugs are memory safety problems.” The Chromium Projects, “Memory-Safe Languages in Chrome”, https://www.chromium.org/Home/chromium-security/memory-safety/(页面持续更新)
Android: “Memory safety bugs are a top cause of stability issues, and consistently represent ~70% of Android’s high severity security vulnerabilities.” Google Security Blog, “Memory Safe Languages in Android 13”, 2022-12-01. https://security.googleblog.com/2022/12/memory-safe-languages-in-android-13.html ↩ -
Discord Engineering, “Why Discord is switching from Go to Rust”, 2020-02-04. https://discord.com/blog/why-discord-is-switching-from-go-to-rust ↩
-
Linkerd, “Under the Hood of Linkerd’s Magic”, Linkerd Docs. https://linkerd.io/2/reference/architecture/#proxy ↩
-
IEEE Computer Society, Guide to the Software Engineering Body of Knowledge (SWEBOK Guide) V4.0, 2024. 当前指南列出 18 个知识领域,包括需求、架构、设计、构建、测试、运维、维护和安全。https://www.computer.org/education/bodies-of-knowledge/software-engineering ↩
-
Miikka Kuutila 等, “Staying or Leaving? How Job Satisfaction, Embeddedness and Antecedents Predict Turnover Intentions of Software Professionals”, ICSE 2026, 2026. 分析 224 名地区多样的软件专业人员横断面调查,报告工作满意度和组织嵌入性与离职意愿负相关。https://arxiv.org/abs/2512.00869 ↩
-
Amy C. Edmondson, “Psychological Safety and Learning Behavior in Work Teams”, Administrative Science Quarterly, 44(2), 1999, pp. 350–383, DOI: 10.2307/2666999。分析 51 个制造团队的心理安全、学习行为和绩效关系。 ↩
-
Ipek Ozkaya, Brigid O’Hearn, “5 Recommendations to Help Your Organization Manage Technical Debt”, Carnegie Mellon University Software Engineering Institute, 2024, DOI: 10.58012/7wn9-tk57。建议使技术债可见、设定目标、建立测量环境并明确分配偿还资源。 ↩
-
Nicole Forsgren 等, “The SPACE of Developer Productivity: There’s More to It Than You Think”, ACM Queue, 19(1), 2021, pp. 20–48, DOI: 10.1145/3454122.3454124。把开发者生产率视为多维构造。 ↩ ↩2
-
John Lunney, Sue Lueder, “Postmortem Culture: Learning from Failure”, in Site Reliability Engineering, O’Reilly Media, 2016. 说明记录贡献原因和防复发行动、关注系统改进而非个人归罪,并在组织层面奖励事后分析和预防。https://sre.google/sre-book/postmortem-culture/ ↩
-
Raymond P. L. Buse, Westley R. Weimer, “Learning a Metric for Code Readability”, IEEE Transactions on Software Engineering, 36(4), 2010, pp. 546–558, DOI: 10.1109/TSE.2009.70。根据 120 名评价者判断构建指标并分析与代码变更和缺陷测量的相关性。 ↩
-
Margaret-Anne D. Storey, Brian Houck, Thomas Zimmermann, “How Developers and Managers Define and Trade Productivity for Quality”, ICSE-SEIP 2022, 2022. 比较开发者和管理者定义生产率与质量及判断权衡的方式。https://www.microsoft.com/en-us/research/publication/how-developers-and-managers-define-and-trade-productivity-for-quality/ ↩
-
Lan Cheng 等, “What Improves Developer Productivity at Google? Code Quality”, ESEC/FSE 2022 Industry Track, 2022. Google 面板分析把代码质量、技术债、工具支持、沟通、目标和组织过程与感知生产率相连,并报告质量改善倾向于先于生产率改善。https://research.google/pubs/what-improves-developer-productivity-at-google-code-quality/ ↩