解构Rust话语
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
目录
- 前言
- 第一Rust的崛起:技术革新与成功叙事
- 第二部分:对核心价值的技术性再评估
- 3. 对“安全性”叙事的多维度分析
- 4. 对“所有权”模型的再评估与设计哲学
- 第三部分:生态系统的现实与结构性成本
- 第四部分:技术社群话语形成之案例研究:Rust生态系统
- 尾声 (Epilogue)
- 附录:技术讨论中观察到的论证谬误案例分析
前言
本书旨在对围绕编程语言Rust的各种技术及社会话语进行批判性的分析与解构。本书不止于阐释语言的语法或功能,而是希望从多维视角审视,被誉为Rust核心价值的“安全性”、“性能”与“并发性”,是在怎样的工程权衡(trade-off)中被选择的;同时,这些概念又植根于何种历史与技术脉络。
为此,本书将涵盖对C++、Ada、Go等多种编程语言的设计哲学与历史的探讨。因此,本书的预设读者虽不必然要求对某一特定语言有精深的专业知识,但应具备对多样化的系统编程范式及计算机科学基本原理的理解。
尤其值得一提的是,在Rust话语中,除了最常被用作比较对象的C++之外,本书还将频繁引用一个相对较少被提及的语言——Ada及其子集SPARK作为重要的参照物。此举并非意在论证Ada/SPARK是Rust在现实中的替代品,而是将其作为一种分析工具(analytical tool)。其目的在于,首先,为“无垃圾回收器(GC)的内存安全”这一核心价值追溯其重要的历史先例;其次,刻意地拓宽技术评估的视野,以超越由主流话语所构建的、与C++之间的二元对立框架。我们认为,这种多维度的比较,对于将Rust的工程学成就与局限性置于一个更客观的坐标系中进行评价,是必不可少的。
在此需要明确,本书批判性分析的对象,并非Rust技术本身,亦非Rust基金会或其核心开发团队的官方立场。事实上我们认识到,在Rust项目的官方渠道中,本书所探讨的诸多技术挑战已被视为重要的改进课题,并且正在积极寻求解决方案。本书的分析对象并非这些官方的改进努力,而是聚焦于在部分线上技术论坛和社交媒体中观察到的特定话语。这种话语的形成与传播方式,正是本书的核心议题。因此,本书的分析旨在促进对技术生态话语结构的客观理解,而非对任何特定群体的指责。基于此,本书中使用的“Rust话语”一词,特指作为分析对象的某种特定倾向,而非整个社区的共识。
本书无意贬低Rust的技术成就及其成功。恰恰相反,我们认为正因为Rust已是一项取得了重要成功的技术,才更有必要对其展开更深刻、更成熟的讨论。本书的最终目标是,通过客观分析工程权衡、理解技术生态中话语的形成机制,帮助开发者超越对特定技术的盲目支持或批判,从而建立一个更为成熟且均衡的视角。
第一Rust的崛起:技术革新与成功叙事
1. Rust语言及其主要特性介绍
1.1 诞生背景:挑战“性能”与“安全”的二元对立
尽管每一种新编程语言的诞生都有其缘由,但像Rust这样,以一种全新的方式挑战既有范式并获得公众广泛关注的案例却不多见。要理解Rust的诞生背景,我们必须首先审视系统编程领域长期以来面临的一个两难抉择。
数十年来,处理底层(low-level)系统的开发者被迫在“性能”与“安全”之间做出痛苦的抉择。 一方面是C/C++这类语言,它们提供了直接操控硬件的强大性能与控制力,但其代价是将分段错误(segmentation fault)、缓冲区溢出(buffer overflow)、数据竞争(data race)等致命内存错误的责任完全推给了程序员。另一边,存在像Ada这样,在语言层面追求高度安全与可预测性,并因此主导了特定高可靠性系统领域的语言。此外,还有像Java、C#这类基于垃圾回收器(GC)的语言,它们通过自动内存管理提供了高级别的安全性,但GC的运行时开销(runtime overhead)和不可预测性,使其无法完全替代实时系统或操作系统内核等所有系统级领域。
Rust,这个始于Mozilla的研究项目,正是以一种果敢的姿态,旨在正面攻克这一“性能还是安全”的世纪难题。其宏伟目标是,创造一门“在拥有C++级别性能的同时,无需GC也能保障高度安全的语言”。为了实现这一雄心壮志,Rust从设计之初便一以贯之地追求安全性、性能和并发性这三大核心目标。
安全性 (Safety)
Rust的核心哲学是内存安全。它旨在通过在编译时严格检查代码的内存使用规则,从根本上杜绝由内存错误引发的各种威胁——如程序异常终止、数据损坏,乃至系统控制权被夺取等致命问题。这是一种全新的思路:不再是责备程序员的失误,而是让编译器强制程序员无法犯错。
性能 (Performance)
作为一门面向系统编程的语言,Rust将卓越的性能视为其核心价值。它的设计不依赖于GC这类笨重的运行时,力求最大限度地发挥硬件性能。“零成本抽象(Zero-Cost Abstractions)”原则鲜明地体现了Rust的设计哲学,即确保开发者在使用高级别的便捷功能时,不会产生额外的运行时开销。
并发性 (Concurrency)
在现代多核处理器环境中,让多个线程在不冲突的情况下安全地共享数据是一项极具挑战性的任务。Rust通过其所有权系统,在编译时就能发现并阻止“数据竞争”这类并发相关的错误。这使得开发者得以体验“无畏并发(fearless concurrency)”。此处的“无畏”,意指基于编译器从技术上根除特定类型错误(如数据竞争)的保证,开发者能以一种心安理得的状态编写并发代码。
综上所述,“为何选择Rust?”这一问题的答案,正位于这三大目标的交汇点。Rust是一次系统的尝试,旨在将性能、安全与并发这三个迄今为止难以兼得的价值,融于一门语言之中。为了实现这一宏大目标,Rust将其独特而强大的“所有权”概念,置于了语言设计的核心。
1.2 通过所有权、借用与生命周期进行内存管理
无需垃圾回收器实现内存安全的核心原理
前文提及的Rust的宏大目标,特别是“无GC的内存安全”,在传统编程语言中几乎被视为不可能的领域。若像C/C++那样依赖程序员手动管理,则难免出错;若像Java那样依赖GC,则必须承受运行时性能的损耗。Rust为了解决此问题,引入了一套独创且核心的机制,它并非在运行时,而是在编译时(compile time)严格强制内存管理规则。这一机制便是由所有权(ownership)、借用(borrowing)和生命周期(lifetimes)三个概念构成的体系。
1. 所有权 (Ownership):万物皆有其主
Rust的内存管理哲学始于一条极为简洁的规则:“所有权”。
- 每一个值(value)都有且仅有一个被称为其所有者(owner)的变量。
- 当所有者离开其作用域(scope)时,该值将被自动释放(drop)。
- 所有权可以通过“移动(move)”转移给另一个变量,转移后,原所有者将不再有效。
这三条规则产生了极为强大的效果。由于一个值只能由其唯一的所有者释放,“二次释放(double free)”错误从根本上变得不可能。同时,当所有权移动后,旧变量便无法再使用,这使得“悬垂指针(use-after-free)”这类错误也能在编译时被彻底阻止。
2. 借用 (Borrowing):无所有权的安全访问
如果“移动”是传递数据的唯一方式,那么每次向函数传递值都会导致所有权的持续转移,这将极其低效和不便。为解决此问题,Rust提供了“借用”机制。它允许在不转移数据所有权的情况下,在特定作用域内临时“借出”对数据的访问权限(即引用,reference)。
然而,这种“借用”必须遵循严格的规则:
- 对于一份数据,可以同时存在多个“不可变借用(immutable borrow,
&T
)”。 - 但是,“可变借用(mutable borrow,
&mut T
)”在任何时候都只能存在一个。当一个可变借用有效时,任何其他(无论是可变还是不可变)借用都是不被允许的。
编译器通过这套规则,在编译时就彻底杜绝了对同一数据进行多处并发修改,或是在读取数据的同时进行修改的企图。这正是Rust从源头上防止“数据竞争(data race)”并实现“无畏并发”的核心原理。
3. 生命周期 (Lifetimes):确保借用数据的有效性
既然存在借用,就需要一种机制来保证所借用的数据在其有效期内始终是有效的。“生命周期”正是扮演了这一角色,它向编译器标示出“借用”(即引用)的有效作用域,也就是其“存活期”。
编译器通过生命周期分析,可以防止因借用数据的所有者提前被释放而导致的“悬垂指针(dangling pointer)”问题。换言之,编译器绝不允许“引用的生命周期长于其引用的数据的生命周期”这种情况的发生。在多数情况下,编译器能够自动推导生命周期,但在一些复杂场景中,开发者需要显式地标注生命周期,以辅助编译器进行分析。
这三个概念——以所有权管理资源生命,以借用实现无数据竞争的安全共享,以生命周期杜绝悬垂指针——共同构成了一个精密的系统。该系统由编译器中一个名为“借用检查器(borrow checker)”的组件强制执行。这个严格的检查器既是Rust陡峭学习曲线的主要原因,同时也是Rust引以为傲的“无性能损耗的安全性”得以实现的核心机制。
1.3 零成本抽象(Zero-Cost Abstractions)的谱系
同时提供高级别的便利性与底层的控制力
在传统编程语言的世界里,“抽象层次”与“性能”长久以来被视为一种权衡关系。像Python或Java这样的高级语言,提供了开发者易于使用的强大抽象功能,但使用这些高级功能时,会不可避免地产生一些不可见的运行时开销(overhead),这被认为是理所当然的。相反,像C语言这样的底层语言,虽然能提供近乎硬件的性能,但开发者必须手动控制一切,并忍受代码可读性与可维护性差的弊端。人们似乎必须在“优雅易读写的代码”和“极致的性能”之间二选一。
C++与Rust对这一长期的权衡关系提出了一个强有力的哲学主张:“不为你未使用的东西付出代价(You don’t pay for what you don’t use)”。这正是“零成本抽象(Zero-Cost Abstractions, ZCA)”原则的核心。ZCA原则指的是,即使开发者使用迭代器(iterator)、泛型(generics)、特质(trait)等高级别的便捷抽象来编写代码,其编译后的最终产物也必须与手动编写的底层优化代码具有同等的性能。
这一原则的最深层根基可以追溯到C语言。C语言通过struct
让开发者直接控制内存布局,通过inline
函数或宏来消除函数调用的开销,为程序员手动地创造零成本代码提供了基础。例如,使用sizeof
运算符在编译时计算数据结构的确切大小,或利用#define
宏在编译前展开重复代码,这些都是C语言中实现高层便利性而无运行时开销的原始ZCA形式。
C++则在此基础上实现了革命性的创新,它在语言层面构建了“安全且可扩展的抽象”。其核心是模板(templates)和RAII(资源获取即初始化)。
- 模板在保证类型安全的同时,能在编译时为多种类型自动生成代码。
- RAII则通过析构函数自动管理资源,从源头上减少了程序员的失误。
Rust正是完整地继承了C/C++的ZCA哲学,并在此之上增加了一道名为“所有权”的强大安全保障。换言之,它将抽象的成本在编译时而非运行时处理以保证性能,同时通过借用检查器强制所有这些抽象都必须遵守内存安全规则。
一个典型的例子是迭代器。请看以下代码:
// 计算1到99中所有3的倍数的平方和
let sum = (1..100).filter(|&x| x % 3 == 0).map(|x| x * x).sum::<u32>();
这段代码通过链式调用filter
, map
, sum
等高级方法,清晰地、声明式地展示了“要做什么”。如果用C语言,则需要使用for
循环、if
条件判断以及一个单独的求和变量来繁琐地实现。然而,Rust编译器能够将这段高级的迭代器代码优化为与手动编写的for循环在性能上无异的机器码。像filter
或map
这类中间调用的开销在编译过程中被完全消除,并且在此基础上,还保证了所有的内存访问在编译时都是安全的。
这种编译时优化,是通过Rust强大的类型系统、泛型,以及编译器积极的内联(inlining)和单态化(monomorphization)等技术实现的。编译器承担了更多的工作,从而让运行时的用户无需支付任何代价。
1.4 通过类型系统和模式匹配确保安全
编译时的严格性,旨在捕获错误
Rust所追求的“安全性”,并不仅仅局限于内存管理。Rust通过其根基性的静态类型系统(static type system),被设计为能够在代码层面显式地表达程序可能遇到的各种状态和错误可能,并由编译器强制执行。这是一种旨在将潜在的运行时错误在编译时提前发现,从而最大化程序整体稳定性的核心策略。此策略的核心,便是Rust强大的类型系统,以及有效处理它的模式匹配(pattern matching)。
Rust类型系统中最耀眼的部分莫过于枚举类型(enum)。不同于其他语言中通常只用于列举常量的enum
,Rust的enum
是一种灵活的数据结构,其每个变体(variant)都可以包含不同类型和数量的数据。Rust利用这一点,以一种极为安全的方式来处理程序中的不确定状态。
一个典型的例子便是解决了空指针(null pointer)问题的Option<T>
类型。Rust中不存在null
。取而代之的是,它使用一个包含Some(value)
或None
两种状态的Option
枚举来表示一个值可能存在也可能不存在的情况。如此一来,编译器便能强制开发者必须处理None
的情况,从而在编译时就杜绝了“空指针解引用”这类运行时错误的发生可能。同理,对于可能成功或失败的操作,通过Result<T, E>
类型显式地返回Ok(value)
或Err(error)
状态,从而防止开发者遗漏错误处理。
而安全、便捷地处理这些强大类型的工具,正是模式匹配。Rust的match
表达式,会被编译器强制要求检查Option
或Result
这类枚举的所有可能情况,无一遗漏。这被称为穷尽性检查(exhaustiveness checking)。
let maybe_number: Option<i32> = Some(10);
// `match`表达式强制开发者检查所有可能的情况,
// 这被称为“穷尽性检查(exhaustiveness checking)”。
// 因此,如果遗漏了对`None`情况的处理,将会导致编译错误。
match maybe_number {
Some(number) => println!("数字是: {}", number),
None => println!("没有数字。"),
}
这样一来,程序员忘记处理某种状态或错误情况这类常见失误,便会被编译器友好地提醒“你遗漏了这种情况”并阻止编译。
总而言之,Rust强大的类型系统促使开发者对程序状态进行显式建模,而模式匹配则强制对所有状态进行无遗漏的安全处理。这正是Rust核心设计哲学的典型体现:“将编译器视为一位严苛的协作者,在编译阶段就根除大量可能在运行时发生的潜在错误。”
1.5 生态系统:Cargo与Crates.io
现代化的构建系统与包管理器
任何一门编程语言的成功,不仅取决于其语言本身的优越性,还需要一个强大、完善的生态系统和工具链,来帮助开发者高效地使用它。特别是像C/C++这类传统的系统编程语言,由于缺乏官方指定的包管理器和构建系统,开发者常常需要为每个项目配置不同的工具(如Makefile, CMake等),并耗费大量时间处理复杂的库依赖问题。
Rust为了解决这些问题,从语言设计的早期阶段就将提供现代化的开发环境作为其核心目标之一。其中心便是Rust的官方构建系统兼包管理器Cargo,以及官方包仓库Crates.io。
Cargo不仅仅是编译代码的工具,它是一个管理项目整个生命周期的一体化(all-in-one)命令行工具。开发者可以通过统一的命令,轻松处理以下任务:
- 项目创建 (
cargo new
): 创建一个具有标准化目录结构的新项目。 - 依赖管理: 只需在一个名为
Cargo.toml
的配置文件中声明所需的库(在Rust中称为“crate”)及其版本,Cargo便会自动下载并管理该库及其所有子依赖。 - 构建与运行 (
cargo build
,cargo run
): 只需一行命令即可编译并运行项目。 - 测试与文档 (
cargo test
,cargo doc
): 运行项目内置的测试代码,并基于源代码注释生成整洁的HTML文档。
所有这些工作的中心,是Crates.io。它是一个类似于Node.js的NPM或Python的PyPI的中心化包仓库,全球的Rust开发者可以在这个平台上轻松地分享自己创建的库,并使用他人的库。
总而言之,尽管Rust的学习曲线陡峭,但Cargo和Crates.io生态系统是许多开发者认为Rust“生产力高”的重要原因之一。通过将项目设置、依赖管理、构建、测试等一系列复杂流程统一到一个标准化的工具中,它极大地降低了环境配置和依赖管理的复杂性,旨在让开发者能够全身心地专注于代码本身。
2. Rust成功的驱动力:技术、生态与叙事的融合
在数十年来新语言层出不穷又迅速消逝的激烈竞争中,Rust是如何在短时间内赢得开发者的高度青睐,并获得主流科技公司的战略性采纳的?要回答这个问题,我们需要超越Rust的技术缺陷或话语问题,深入分析驱动其成功的复合动力。
Rust的成功并非单一因素所能解释,而是技术必然性、创新的开发者体验、强大的叙事以及时代需求精妙结合的产物。本章将逐一分析这些核心驱动力,旨在阐明Rust如何在现代软件开发生态中占据重要地位。
2.1 技术必然性:对“看似无解问题”的解答
Rust成功的最根本动力,在于它为系统编程领域的长期难题——“无性能损耗的内存安全”——提供了一个实用且强有力的答案。
数十年来,开发者为了获得C/C++提供的高性能和直接硬件控制权,不得不忍受内存错误这一顽疾。而像Java或C#这样基于垃圾回收器(GC)的语言,虽然提供了内存安全,但其GC不可预测的停顿(pause)和运行时开销,使其无法全面替代操作系统、浏览器引擎、实时系统等所有系统领域。
Rust正面突破了这一二元对立的格局。通过所有权和借用检查器这一编译时静态分析模型,它开辟了一条在维持与C++相媲美的运行时性能的同时,无需GC也能从根本上防止致命内存错误的道路。这不仅仅是技术上的改进,更是打破了“安全与性能相互冲突”这一既有范式,提供了技术上的必然性(technical justification)。尤其是在“心脏出血(Heartbleed)”等大型安全事故之后,当业界对内存安全的需求达到顶峰时,Rust作为最适时、最具说服力的解决方案应运而生。
2.2 开发者体验(DX)的革新:Cargo与现代工具链
无论一门语言多么出色,如果使用起来困难不便,也难以被广泛采纳。在探讨Rust的成功时,一个不可或缺的核心要素便是以其官方构建系统兼包管理器Cargo为中心的现代化开发者体验(Developer Experience, DX)。
C/C++生态系统数十年来一直饱受Makefile
、CMake
、autotools
等碎片化构建系统和非标准化依赖管理问题的困扰。与此形成鲜明对比的是,Rust从设计之初就提供了一个统一且一致的工具链。开发者只需通过cargo new
、cargo build
、cargo test
等几个简单命令,就能完成项目创建、依赖管理、构建、测试和文档生成等所有工作。
这是一种极大降低开发流程摩擦的革命性变革。正如npm
引爆了JavaScript生态、pip
引爆了Python生态一样,Cargo是驱动Rust生态系统快速增长的核心基础设施。尽管Rust的学习曲线陡峭这一缺点显而易见,但其之所以被开发者评价为“生产力高”,强大而便捷的工具链起到了决定性作用。
2.3 强大叙事的构建与“议程设置”的成功
一项技术的成功,并非仅由其技术优越性决定。围绕该技术的叙事(narrative)是否具有吸引力和说服力,在很大程度上左右着公众的认知。Rust在这一点上采取了极为成功的策略。
- 清晰的价值主张: “无畏并发(fearless concurrency)”、“无性能损耗的安全性”等简洁有力的口号,清晰地传达了Rust旨在解决的问题及其价值。
- “议程设置(agenda-setting)”的成功: Rust话语通过构建与C/C++的对立格局,成功地将“内存安全”提升为评价系统编程语言的最重要标准。通过将一个本应是理所当然的价值推向讨论的中心,Rust为自己创造了一个最为有利的竞争舞台。这可以被分析为一个技术社区成功地围绕特定价值塑造公众认知并掌握话语权的“议程设置”案例。
这种强大的叙事为开发者提供了学习和使用Rust的明确动机,并成为社区内部形成强烈身份认同感和自豪感的向心力。
2.4 战略性赞助与开放的社区文化
与其他许多由个人或小团体主导的语言不同,Rust从诞生之初就得到了Mozilla这一具有高度公信力的机构的赞助。这为项目的稳定性和长期发展提供了信任保障。随后,谷歌、微软、亚马逊等公司参与创立的Rust基金会(Rust Foundation)更是巩固了其地位。这种来自强大机构和企业的赞助,使得Rust被广泛认知为一个旨在解决工业界核心问题的严肃项目,而非一个简单的业余爱好项目。
与此同时,Rust项目官方采纳了行为准则(Code of Conduct),并强调建立一个欢迎新成员的、开放包容的文化。特别是像《The Rust Programming Language》(通常被称为“The Book”)这样系统而友好的官方文档,获得了试图学习复杂概念的开发者们的高度评价,极大地降低了入门门槛。
2.5 结论:成功驱动力的协同效应
总而言之,Rust的成功并非源于单一因素,而是上述所有驱动力协同作用的结果。
- 它针对一个核心问题(“无性能损耗的安全性”),
- 提出了一个明确的技术解决方案,
- 并以创新的开发者体验(
Cargo
)作为支撑, - 通过强大而富有吸引力的叙事传播其价值,
- 最后在高公信力机构的赞助和开放的社区支持下,奠定了可持续生态系统的基础。
对这些多维度成功驱动力的理解,为本书后续章节中关于Rust技术局限性和话语问题的探讨,提供了一个进行更均衡评价的必要背景。Rust的成功绝非偶然,其成功方程式本身,就蕴含着其他技术和社区可以学习的重要经验。
第二部分:对核心价值的技术性再评估
在第一部分中,我们探讨了Rust作为一门成功的语言其崛起的技术特性与成功叙事。在第二部分中,我们将更进一步,对被誉为Rust最核心价值的“安全性”与“所有权”进行技术性的再评估。
我们将多维度地分析,这些价值在被誉为“革新”的背后,究竟存在何种工程权衡(trade-off);同时,这些概念又是如何在C++与Ada等编程语言的历史中,继承并发展了哪些先例。本部分旨在通过这些分析,为更深刻、更均衡地理解Rust的核心设计哲学,奠定一个批判性的基础。
3. 对“安全性”叙事的多维度分析
3.1 “革新”之意义的重构:与历史先例(Ada, C++)的比较
Rust因其同时实现了“性能”与“安全”这两个相互冲突的目标,而被誉为改变了系统编程长期设计哲学的“革新”。然而,这一“革新”是否等同于“前所未有的发明”,有必要从工程学和历史学的角度进行严谨的审视。
软件工程的历史并非一系列孤立发明的串联,而是通过对既有思想的继承与发展而连接成的宏大流脉。本节旨在阐明,被视为Rust核心革新的诸多概念,实际上植根于何种历史先例。我们将基于C++、Ada以及函数式语言留下的重要技术遗产,对Rust进行重新审视。
所有权与资源管理:对C++ RAII哲学的继承
被誉为Rust最独创特性的“所有权(ownership)”模型,实际上位于C++数十年来为解决资源管理问题而探索的答案的延长线上。C++早已确立了RAII(Resource Acquisition Is Initialization,资源获取即初始化)这一极其强大的设计模式,它将资源的生命周期与对象的生命周期绑定,在析构函数被调用时自动释放资源,并通过智能指针将其具体化。
换言之,通过“所有权”来安全管理资源这一思想本身,是由C++率先在概念上建立并发展起来的。Rust的真正贡献在于,它将这一思想从一种“可选择的模式”,变为了由编译器在语言所有领域中“强制执行的规则”。这与其说是概念的“发明”,不如说是一次对既有优秀哲学的更严格、更全面的应用,是一次“伟大的整合”与“范式的强化”。(关于C++的RAII和智能指针的更详细分析,将在4.1节中继续。)
无GC的安全性:Ada/SPARK的先例
Rust的另一个核心身份是“无垃圾回收器(GC)的内存安全”。然而,这一目标同样并非Rust在人类历史上首次挑战。诞生于1980年代、由美国国防部主导的Ada语言,从一开始就是为航空、国防、航天等高可靠性(high-integrity)系统而设计的。
Ada通过其强大的类型系统和运行时检查,早已在无GC的情况下防止了空指针访问、缓冲区溢出等大量错误。更进一步,Ada的子集SPARK引入了形式化验证(formal verification)技术1。这是一种通过数学方式证明程序特定属性(如:无运行时错误)的技术,其提供的可靠性水平远超Rust借用检查器所提供的内存安全保证。(对此的详细比较将在3.4节中展开。)
当然,Rust的借用检查器以一种比形式化验证远为自动化和易用的方式解决了内存安全问题,其实用价值巨大。但我们无法否认,“无GC实现安全”这一目标本身,早已存在一个由Ada/SPARK生态系统在数十年前于更高层面上追求并实现的历史先例。
显式错误处理:函数式编程的遗产
此外,Rust通过Result
和Option
实现的显式错误处理方式也并非全新。它成功地借鉴了Haskell、OCaml等ML系函数式语言发展了数十年的代数数据类型(Algebraic Data Type, ADT)和单子化(Monadic)错误处理技术。这些语言早已通过类型系统来显式地表示“值缺失状态”或“错误发生状态”,并强制编译器处理所有情况,以此来提升程序的稳定性。
作为伟大整合者的Rust
由此可见,Rust的核心思想并非诞生于真空中。它成功地借鉴并整合了编程语言历史长河中诸多闪光的思想:C++的RAII与所有权哲学、Ada/SPARK对无GC安全性的追求,以及函数式语言基于类型的错误处理(Result
/Option
)。
因此,Rust的“革新”并非“从无到有的创造”,而在于它将这些散落的伟大概念熔于一炉,并通过编译器这一强大工具,将其“强制”给所有开发者,从而达到了前所未有的“普适性安全”水平。这并非贬低Rust的价值,而是在历史的脉络中,对其工程学成就进行更准确的评价。
3.2 实用性革新:价值的普及化及其话语功能
在前述3.1节中,我们分析了Rust的核心概念深植于C++、Ada等先驱技术。对此,Rust的拥护者们可能会提出反驳,认为其革新的本质并非“概念的发明(conceptual invention)”,而在于“价值的普及化(democratization of value)”。
该主张的核心逻辑如下:Ada/SPARK所追求的高级别安全性,要求极高的成本(陡峭的学习曲线、复杂的专业工具、缓慢的开发速度等),只有航空、国防等极少数特殊领域能够承担。结果,这种价值对绝大多数普通开发者而言并无实际意义。相比之下,Rust通过现代化的构建系统Cargo带来了卓越的开发者体验(DX)、相对易于获取的学习资料以及活跃的社区,成功地将此前仅为少数专家所能企及的“无GC内存安全”这一价值,推广到了常规的系统编程领域。换言之,一种能为多数人所用的“足够好(good enough)”的技术,比一种仅为少数人能用的理论上的完美,具有更大的工程学和实用意义上的革新性。
这一观点准确地捕捉了Rust对软件工程生态系统的重要贡献。Rust在改善开发者体验、拓宽对内存安全重要性认知方面的功劳是显而易见的,其本身就值得高度评价。
然而,本书关注的焦点在于,这种“实用性革新”的主张在技术话语内部的运作方式。该主张本身虽具合理性,但有时我们观察到,它倾向于成为一种修辞工具,用以回避对其“概念原创性缺失”的批判性诘问。当面对“A在概念上是新的吗?”这一问题时,若回答“A在市场上很成功且易于使用”,那么即便后一陈述是事实,它也并未直接回答前一个问题。这可能构成一种论点转移,将讨论的范畴从“概念的起源”切换到了“实用性的效益”。
这种逻辑转换,可能导致一种基于Rust的“实用成功”,进而暗示其“概念唯一性”的话语。其结果是,像Ada和C++这类语言经过数十年所取得的历史性、工程性成就,有时会被不公地贬低,或是在讨论中被排斥。
总而言之,“实用性革新”这一概念,在解释Rust重要成就的同时,也具有回避对“革新”一词本义进行批判性审视的话语功能,呈现出一种双重性。这是分析在强调某一技术优越性的过程中,术语的含义如何被重新定义、讨论的焦点如何被策略性地转移的一个重要案例。
3.3 “内存安全”的定义与“内存泄漏”问题分析
“内存安全”是构成Rust话语核心的价值,但只有在明确其定义和适用范围时,才能进行更精确的分析。Rust提供的内存安全保证,在系统编程领域被评为一项重要成就,然而该术语并非涵盖所有类型的内存相关问题。
Rust编译器在编译时静态地阻止了那些可能导致未定义行为(Undefined Behavior, UB)的致命内存错误,例如空指针(null pointer)解引用、释放后使用(use-after-free)和数据竞争(data race)。与C/C++相比,这是Rust明确的技术优势。
然而,Rust的安全保证中存在一个值得注意的例外,那就是内存泄漏(memory leak)。内存泄漏指程序分配的内存未被释放,导致系统可用内存逐渐减少的现象,这在长期运行的服务中可能引发严重问题。使用Rc<T>
和RefCell<T>
这类共享指针时产生的循环引用(reference cycle),是导致内存泄漏的典型案例,而这在Rust中被归类为“安全(safe)”代码。
这种技术局限本身是多种采用引用计数方案的语言中普遍存在的已知问题。本书希望关注的焦点在于,Rust的“内存安全”这一术语是如何被定义和传播的——即其话语结构。
根据Rust的官方文档“Rustonomicon”,Rust中“安全(safety)”所保证的是:“安全代码绝不会引发未定义行为(UB)”2。 按照此定义,内存泄漏并非导致程序行为不可预测的UB,因此不包含在Rust的安全保证范围内。这是一个技术上清晰的定义。
问题产生于这种严谨的技术性“定义”与普通开发者对“内存安全”一词所期待的普适性“认知”之间的鸿沟。“内存安全”这个术语,常常被泛化地理解为“所有与内存相关的问题都已解决”。这种认知上的不一致,可能导致那些未能准确理解Rust安全保证具体范围的人,产生一种Rust已经解决了所有内存问题的印象。
这种话语的形成方式,与其他语言社区的文化存在显著差异。例如,在C语言社区中,内存管理完全是开发者的“责任”这一点是明确共识。包括内存泄漏在内的所有内存相关讨论,都被视为待解决的“技术挑战”而得到活跃的探讨。
相比之下,在一些线上技术讨论中,我们观察到一种倾向,即以“工具的保证范围”为由,将内存泄漏这类特定问题的讨论视为“偏离主题”而加以回避。这可被看作是一种将问题解决的责任,从内化为“开发者的能力”,转变为用“工具保证范围之外”的逻辑进行切割的思维方式。这种思维方式的差异,不仅揭示了各语言设计哲学的不同,更体现了社区在认知和讨论技术局限性方式上的差异,是一个重要的观察点。当面对特定问题(如内存泄漏)的批评时,这种技术定义与大众认知之间的鸿沟,便成为了一种防御机制的背景,它将问题本身定义为“偏离主题”,从而回避讨论(参见8.4节案例研究2)。
3.4 保证的层级:Ada/SPARK的数学证明与Rust的局限
Rust的安全模型相较于C/C++取得了重要进步,这一点是明确的。但要客观评价其保证的层级,我们需要在更广阔的谱系中理解“安全性”这一价值。本节将如前言所述,利用Ada/SPARK作为一种“分析工具”,旨在明确Rust的安全模型在整个系统编程语言安全保证谱系中的定位。也就是说,通过与SPARK提供的“经数学证明的正确性”进行比较分析,来探究Rust的“实用性安全”模型具有何种工程价值与局限。此比较并非旨在评判语言优劣或讨论现实的替代方案,而是为了清晰地理解不同设计哲学所做出的权衡。
Rust的安全保证:防止“未定义行为(UB)”
Rust的核心安全保证,是通过“所有权”和“借用”规则,在编译时防止由内存访问错误和数据竞争(data race)所导致的未定义行为(Undefined Behavior, UB)。这意味着只要代码通过编译,原则上就不会发生这类错误。并且,这是在“零成本抽象”原则下,无需牺牲运行时性能而达成的,因而被视为重要的工程成就。
然而,Rust的编译时保证也集中于此。它并不保证程序整体的逻辑正确性(logical correctness),也不保证杜绝所有类型的运行时错误(runtime error)。例如,整数溢出、数组索引越界等错误仍可能发生,而这些错误不会导致不可预测的状态,而是导向一种被称为“恐慌(panic)”的受控程序中止。这与保障系统的稳定“运行”是不同层面的问题。
Ada/SPARK的安全保证:证明“程序正确性”
相比之下,Ada/SPARK生态系统旨在实现更广泛的正确性。
- Ada的基本安全性与恢复力:Ada在语言层面通过类型系统和“契约式设计(Design by Contract)”来尝试防止逻辑错误,特别是,它默认在发生包括整数溢出在内的各类运行时错误时,会抛出异常(exception)。这不仅仅是中止程序,而是一种旨在通过错误处理程序使系统能继续执行任务的、以“恢复力(resilience)”为导向的设计。这与Rust将错误视为“不可恢复”并中止线程的
panic
哲学,在根本目标上存在差异。 - SPARK的数学证明:Ada的子集SPARK更进一步,通过形式化验证(formal verification)工具对代码的逻辑属性进行数学分析。借此,它可以在编译时“证明”运行时错误(包括整数溢出、数组索引越界等)从根本上不会发生。
两种语言的保证层级比较
错误类型 | Rust | Ada (基本) | SPARK |
---|---|---|---|
内存错误 (UB) | 编译时阻止 (保证) | 编译/运行时阻止 (保证) | 数学证明不存在 |
数据竞争 | 编译时阻止 (保证) | 编译/运行时阻止 (保证) | 数学证明不存在 |
整数溢出 | panic (不可恢复中止) 或 环绕 (取决于配置) |
运行时异常 (可恢复) | 数学证明不存在 |
数组越界 | panic (不可恢复中止) |
运行时异常 (可恢复) | 数学证明不存在 |
逻辑错误 | 程序员责任 | 通过契约式设计部分防止 | 可根据契约证明不存在 |
结论:安全性谱系中Rust的定位
总而言之,该比较分析在承认Rust安全性是重要进步的同时,也表明了它并非“安全性”谱系中唯一或最终的标杆。SPARK为追求“最高级别的保证”,要求开发者付出明确的证明努力和使用专业工具的高昂成本;而Rust为实现“普适且自动化的安全”,则选择性地限定了部分保证范围,并以开发者的学习曲线为代价。也就是说,这两种技术面向的目标市场和开发环境不同,与其说是直接的竞争关系,不如理解为它们是针对不同工程问题的相异解决方案。
因此,在评价Rust的安全性时,若仅以C/C++为唯一比较对象,这种话语在将Rust的技术定位放置于整个系统编程历史中进行准确把握时,会存在局限。为了达成更成熟的理解,与多种技术替代方案进行多维度的比较是必不可少的。
3.5 比较对象的现实:演进中的C++多层安全网
强调Rust安全性的论述,往往通过与C/C++的比较来证明其价值。在此过程中,C/C++常被描绘为解决内存问题失败的“过往的语言”。然而,为使这种比较具有合理性,其比较对象不应是1990年代的C/C++,而应是经历了无数次演进的“现代C/C++生态系统”。
在过去二十年间,C++语言及其生态系统为确保安全性,已构建起一种多层次的(multi-layered)方法。但这种安全网与Rust内建于编译器的保证不同,它依赖于开发者的自觉选择、额外的成本投入以及严格的纪律,这构成了根本性的差异。
1. 语言的演进:现代C++与智能指针的“选择性”安全
首先是语言自身的演进。自C++11标准之后,“现代C++”在标准库中引入了智能指针(std::unique_ptr
, std::shared_ptr
),在语言层面积极支持RAII模式。这是一种有效的方法,它明确了资源的所有权并自动管理内存,从而在语言级别防止了过去C++中相当一部分与内存相关的顽固问题。
然而,在C++中,智能指针的使用并非强制,而是一种“最佳实践(best practice)”。开发者随时可以不遵循这一模式而使用裸指针(raw pointer),编译器并不会阻止。也就是说,安全性的最终责任,仍然依赖于开发者的纪律,因此失误仍可能发生。
2. 工具生态的成熟:要求“成本与专业性”的多层防御
其次是成熟的工具生态系统的支持,它涵盖了静态与动态分析。如今,专业的C/C++开发环境可以通过以下自动化工具来保障安全性:
- 静态分析:在编译前对代码库进行精密分析以发现潜在错误的工具,如 Clang Static Analyzer, Coverity, PVS-Studio 等被广泛使用。
- 动态分析:在程序实际运行时监控内存访问,从而在运行时检测出仅靠静态分析难以捕捉的细微内存错误的工具,如 Valgrind、地址消毒器(AddressSanitizer)等,扮演着重要的安全网角色。
- 实时提示(Linting):像 Clang-Tidy 这样的Linter,在开发者编码的瞬间就能指出潜在错误,提供实时反馈。特别是,它能强制执行C++核心准则(C++ Core Guidelines)3中的许多规则,引导更安全的编码风格。
这些强大的工具虽能显著提升安全性,但其中强大的商业工具通常价格不菲,且正确配置和解读分析结果也需要相当的专业知识。这与Rust官方工具链(cargo
)无需额外费用即默认提供静态分析功能,在易用性和普适性上存在根本差异。
3. 关键任务系统的实践:特定领域的严苛纪律
第三,在如汽车、航空、医疗设备等要求极高可靠性的“关键任务(mission-critical)”系统领域,会采用远为严苛的方法论。
- 强制编码标准:通过 MISRA C/C++ 等编码标准,从源头上禁止使用危险的语言特性。
- 明确代码契约:使用SAL、ACSL等注解语言为代码添加明确的“契约”。
- 执行静态验证:通过 Polyspace、Frama-C 等静态代码验证工具,对运行时错误的可能性进行数学上的验证。
- 执行编译器验证(compiler validation):在航空(DO-178C)或汽车(ISO 26262)等安全标准中,要求一个证明编译器已将源代码正确转换为机器码的过程。这在Ada或C/C++生态中,通过专业厂商提供的“验证套件(Qualification Kit)”来实现,其可能性建立在语言的标准化和成熟的商业生态之上。相比之下,Rust在支持这类官方安全标准认证(如ISO 26262)的工具和供应商生态方面,其成熟度尚不及C/C++(参考7.2节)。
这些方法证明了C/C++代码可以被打造得具有极高的安全性。但这仅限于极少数特殊领域,若应用于一般软件开发,则会伴随巨大的成本和精力,严重影响开发效率。
结论:“选择性努力”与“强制性默认”的差异
现代C++生态系统为解决自身问题而发展出了一套精密、多层的安全保障方法,这是不争的事实。更进一步,这种发展已不只是对既有问题的防御性应对,而已然导向了语言本身根本性的“演进”。例如,C++23标准中引入的std::expected
,便是一次像Rust的Result
类型那样,将错误值作为类型系统的一部分进行显式处理的尝试,这展现了不同编程范式间积极的思想交流,是一个重要的案例。
然而,正是在这一点上,C++的根本方法与Rust的价值被清晰地划分开来。在C++中,使用std::expected
这类安全功能,仍然是依赖于开发者“选择”的“最佳实践”。这意味着它要求开发者付出选择性的努力,有时还需辅以昂贵的外部工具或严苛的纪律。现实是,在绝大多数项目中,这些最新的标准和方法论并没能得到一以贯之的应用,而这正是为何内存相关的安全事故至今仍层出不穷的原因。4
总而言之,Rust内建于编译器的安全机制,与C++的多层安全网相比,其根本差异在于,它将“安全性”这一价值从少数专家的选择性努力,转变成了面向所有开发者的强制性默认值(enforced default)。在尊重现代C++安全保障方式的同时,我们必须理解,正是这些方法论未能得到普遍应用的“现实”,才使得像Rust这样的替代方案获得了强大的说服力。
3.6 替代性内存管理方案:对现代垃圾回收的再评估
在评价Rust的内存管理方式时,最常被用作比较对象的是C/C++的手动管理。然而,在系统编程的广阔谱系中,通过垃圾回收器(Garbage Collector, GC)来同时实现内存安全和高生产率的语言(如Go, C#, Java)同样占据着重要位置。
在部分Rust话语中,GC因其不可预测的“Stop-the-World”停顿现象和运行时开销,而被论断为不适用于某些系统编程领域。这类主张在GC技术相对不成熟的过去或许有其合理性,但可能未能充分反映过去二十年间飞速发展的现代GC的特性。
如今,主流语言搭载的GC通过分代(generational)GC、并发(concurrent)GC、并行(parallel)GC等精密技术,在管理内存的同时,已能将对应用程序执行的干扰降至最低。例如,Go语言的GC以微秒(µs)级的极短暂停时间为设计目标,已成为无数高性能网络服务器和云基础设施的基石。Java阵营的ZGC或Shenandoah GC等最新GC,其目标是在高达数百GB的堆内存中,也能实现毫秒(ms)级的暂停时间(参考相关官方文档),这正是在挑战“GC会暂停系统”的传统观念。
归根结底,将Rust的所有权模型与现代GC进行比较,与其说是在评判绝对的优劣,不如将其理解为对“成本支付方式”的设计哲学差异,会更为准确。
- Rust的方案:将运行时成本最小化,但将此成本转移至编译时间和开发者的学习曲线,即“开发时间”。开发者必须投入认知努力来学习和遵守所有权与生命周期规则。
- 现代GC语言的方案:减少开发者的认知负担和开发时间,但其代价是在运行时消耗少量的CPU和内存资源,即“机器时间”。
当然,在硬件约束极其严苛的嵌入式系统或硬实时(hard real-time)操作系统等领域,GC的存在本身就是一种负担,这样的领域确实存在。但将这种特定需求普遍化,从而低估所有GC语言的实用性,可能是一种忽视多样化商业环境需求的行为。在大量的商业场景中,开发速度和产品上市时间(time-to-market)比原始的运行时性能更为重要。在此情况下,能将开发生产率最大化的GC语言,是一种非常合理且经济的选择。
3.7 unsafe
的悖论:C ABI依赖与被保证安全的边界
Rust的编译时安全保证,仅在被归类为“安全(safe)”代码的区域内有效。然而,Rust通过unsafe
关键字,提供了一条可以有意绕过编译器严格规则的明确路径。unsafe
的存在并非一个简单的例外功能,而是揭示Rust安全保证边界何在、以及该边界如何与外部世界连接的核心概念。
强制要求unsafe
关键字存在的最根本原因,是与外部语言互操作,即FFI(Foreign Function Interface)的现实。当今所有主流操作系统、硬件驱动以及数十年积累下来的核心库,都将C语言的ABI(Application Binary Interface)作为事实上的标准接口。一个Rust程序若要执行任何有意义的操作——如读取文件、进行网络通信、或是在屏幕上绘制图形——终究必须调用以C ABI实现的操作系统API。
正是在这一点上,Rust的根本性悖论产生了。Rust宣称是解决C/C++内存问题的“替代品”,但讽刺的是,为了执行其功能,它又必须在结构上依赖于作为C/C++生态基石的C ABI。使用FFI本质上意味着解除Rust的安全网,并接受C的内存模型,这一过程不可避免地要求使用unsafe
块。因为Rust编译器无法验证FFI边界另一侧的C代码是否会遵守其约定(例如:指针绝不为null,缓冲区足够大)。
结果是,Rust试图解决的那些危险(空指针、缓冲区溢出等)又可能通过FFI这一“不安全的边界”重新流入Rust程序。这揭示了为何“用Rust重写一切(RIIR, Rewrite It In Rust)”这一口号在现实中难以实现的结构性局限。
当然,unsafe
也用于FFI之外的其他目的:
- 与底层硬件及OS的交互:如不通过C ABI直接控制硬件寄存器等。
- 克服编译器分析模型的局限:即便是像
Vec<T>
这样的标准库核心数据结构,其内部为了实现借用检查器无法证明的高度优化的内存管理,也使用了unsafe
代码。
特别是这种在内部使用unsafe
代码,同时向用户提供“安全”接口的模式,是Rust生态中广泛使用的一种重要模式。然而,正是这种模式的结构留下了一个重要问题:如果那个“安全”接口内部的unsafe
实现存在bug,责任归谁?要回答这个问题,当我们比较责任归属的方式与C/C++生态文化有何不同时,答案会更加清晰。
在C/C++生态中,一个库的内存bug,往往被视为语言本身固有的、广为人知的风险的体现。因此,关于bug的讨论主要聚焦于其技术原因和解决方案,这常被看作是再次确认了语言根本上的“不安全性”。责任虽在制造bug的开发者,但其失败是在语言的本质风险这一大框架下被理解的。
相比之下,Rust将“Safe Rust
保证内存安全”这一强大而明确的语言承诺,作为其核心身份。因此,当unsafe
代码中发生内存错误时,相关的讨论除了批评编写bug的开发者个人外,还倾向于执行一种话语功能,即捍卫“Safe Rust
的安全保证未被破坏”这一核心叙事。也就是说,失败的原因被明确地切割并归结为“开发者应保证的unsafe
区域内的人为失误”,而非“编译器所保证的系统性失败”。
这种在维持Safe Rust
完整性的同时,将失败责任限定并归结于unsafe
代码编写者个人的逻辑结构,是揭示Rust安全模型如何被认知和捍卫的一个独特之处。
3.8 “安全失败”的意义:panic
与系统健壮性的关系
在讨论Rust的安全模型时,一个常被提及的主张是“Rust即使失败,也是安全地失败”。要精确分析这一主张的含义,我们首先需要从两个不同角度来区分“失败”一词:
- 从内存完整性角度的“安全失败(safe failure)”:指不引发未定义行为(UB)或数据污染,而是以一种受控的方式终止程序的失败。
- 从服务连续性角度的“不可恢复的中止(unrecoverable halt)”:指错误发生时,无法通过异常处理等方式恢复逻辑或维持服务,而是导致相关线程或进程直接终止的状态。
Rust的panic
,从前一个角度看,无疑是“安全失败”;但从后一个角度看,则属于“不可恢复的中止”5。 本节旨在分析panic
的这种双重特性,与系统的整体健壮性(robustness)及恢复力(resilience)之间存在何种关系。
panic
与分段错误的技术差异及用户视角
技术上,panic
与分段错误截然不同。分段错误可能导致内存污染或不可预测的二次损害,而Rust的panic
在默认情况下会安全地栈展开(unwinding),调用每个对象的析构函数(drop
),并以一种受控的方式终止程序。这一过程保留了数据的完整性,并便于调试,具有明确的工程学优势。
但若将视角从“开发者”转向“最终用户”,这种技术上的优雅则呈现出另一层含义。对用户而言,程序的突然中止,无论其原因是受控的panic
还是不可预测的崩溃,本质上都是同一种“服务失败”。因此,若将panic
的技术优势解读为“安全性”的最终胜利,则可能忽视了另一个重要的价值——“系统的持续生存”或“服务的恢复力”。
“安全失败”对开发文化的影响
更进一步,“安全失败”得到保障这一事实,可能对开发文化产生一种悖论性的影响。
在C/C++开发环境中,由于内存错误可能导致不可预测的灾难,一种旨在防御潜在错误、处理所有异常情况的“防御性编程(defensive programming)”文化常被强调。
相比之下,在Rust中,“最坏情况下也能在无数据污染的情况下安全终止”的panic
的存在,可能会诱使开发者选择使用.unwrap()
或.expect()
来明确地引发panic
,而非用Result
类型来精细地处理所有错误。有批评指出,这可能导致一种倾向于在问题发生时选择“中止”,而非努力从复杂错误中“恢复”系统的开发方式。
总而言之,panic
作为一种失败处理方式,在数据完整性保持和调试便利性方面具有明显优势。但“安全失败”这一概念,可能在某种程度上削弱了开发者去设计能从错误中恢复并持续服务的、具有整体健壮性的程序的努力,这一点值得我们批判性地审视。这最终引出了一个重要问题:工具的特性如何影响开发者的设计哲学与文化。
3.9 “安全性”保证的范围及其局限
通过以上多角度的分析,我们最后有必要明确Rust安全保证所覆盖的范围(scope)。在Rust话语中,“安全性”被反复强调,这有时可能导致该术语被扩大解释为“免于所有类型bug的安全”。然而,Rust编译器提供的保证,主要集中于“内存安全(memory safety)”这一特定领域。
以下是一些编译器无法检测,因而完全由开发者负责的主要bug类别:
- 逻辑错误(logical error) 这是软件中最常见的错误类型。指程序逻辑本身出错,例如重复应用折扣率,或在金融计算中用错利率。Rust的类型系统和借用检查器虽然能验证内存访问的有效性,但无法验证代码的业务逻辑是否“按预期正确”工作。
- 整数溢出(integer overflow)
在调试构建(debug build)中,整数溢出会引发
panic
,但在性能优先的发布构建(release build)中,默认行为是值的环绕(wrapping)。这虽是预期的设计规格(参考:The Rust Book, “Integer Overflow”),但若开发者未显式处理,可能成为导致意外数据损坏或计算错误的逻辑bug源头。 - 资源耗尽(resource exhaustion)
- 内存泄漏(memory leak):如前3.3节所述,使用
Rc<T>
和RefCell<T>
等导致的循环引用,尽管被归类为“安全(safe)”代码,仍可能引发内存泄漏。 - 其他资源:如文件句柄、网络套接字、数据库连接等有限的系统资源,在使用后未释放所导致的问题。Rust的RAII模式(
Drop
trait)虽有助于资源释放,但这取决于开发者是否为相应类型正确实现了Drop
,语言本身并不自动保证所有类型的资源管理。
- 内存泄漏(memory leak):如前3.3节所述,使用
- 死锁(deadlock) Rust的所有权系统能有效防止多个线程同时写入同一数据所导致的“数据竞争(data race)”。但它无法防止两个或更多线程各自占有一个资源(如互斥锁),同时又无限等待对方资源所导致的“死锁”。因为这并非内存安全问题,而是并发设计中的逻辑问题。
总而言之,Rust能在编译时有效阻止C/C++中顽固的内存错误类别,这是一项重要的工程成就。尽管如此,这并不保证能产出“无bug的软件”。确保软件的整体正确性(correctness)和可靠性(reliability)的责任,无论使用何种工具,最终都取决于开发者的设计能力和严格的测试。
3.10 性能、安全、生产率:编程语言设计的权衡
在前述各节中,我们考察了Ada/SPARK、现代C++生态以及基于垃圾回收(GC)的语言等多种技术选项。所有这些讨论都归结于软件工程的一个根本原则,即:“不存在能够同时以最佳方式满足所有需求的、唯一的完美工具,即‘银弹(silver bullet)’”。
所有的工程设计,本质上都是一个关于权衡(trade-off)的选择过程。编程语言通常也在性能(performance)、内存控制(memory control)和开发生产率(developer productivity)这三个主要轴构成的空间中,占据各自的位置。要同时将这三项价值都推向极致在现实中是困难的,因此,每种语言及其生态都会根据自身哲学,选择不同的定位。
- 极致的性能与控制 (C/C++): 以牺牲部分开发生产率和编译器级别的安全性为代价,将最大限度地利用硬件性能和直接控制内存作为首要目标。开发者以获得强大控制权为交换,承担了更多的责任。
- 高开发生产率 (Go, Java/C#): 以让渡部分绝对性能和直接内存控制权为代价,通过GC和丰富的运行时,将开发速度和产品上市时间最大化。这在绝大多数Web服务和企业应用环境中,被视为最经济、最合理的选择。
- 同时追求性能与安全 (Rust): Rust正是在这两个群体之间的缝隙中寻求突破。它力求在维持C++级别性能的同时,无需GC也能确保内存安全。其代价是,开发者必须为学习和遵守借用检查器模型,而支付“高昂的认知成本”,即牺牲了部分“开发生产率”。
此处值得注意的是,特定技术话语是如何处理这种工程权衡的。Rust所选择的“同时追求性能与安全”这一独特定位,本身具有重要价值,但很难说它是在所有问题场景下的最优解。因为在具有不同约束条件的现实项目中,根据具体情况,其他语言为某一价值而折衷另一价值的设计方式,可能才是更合理的选择。
- 对于快速上市至关重要的Web服务后端:Rust的高学习成本和部分Web框架生态的相对不成熟,可能与商业目标相冲突。在这种情况下,Go简洁的并发模型和快速的编译速度,或是C#/.NET庞大的企业生态所提供的高生产率,可能是更好的选择。用“足够好(good enough)”的性能更快地创造商业价值,在工程上可能是更优的决策。
- 对于要求最高级别可靠性的特殊系统:本书用作分析工具的Ada/SPARK等语言,则展示了另一维度的权衡。它们为了满足如飞机控制系统或核电站这类不允许任何一个运行时错误的环境,将“可被数学证明的稳定性”置于所有其他价值之上。为此,它们选择了承受远为高昂的开发成本和努力6。 这与Rust提供的“实用性安全”在目标定位上不同,是一种特殊用途的选择。
- Rust成为最优选择的场景:相比之下,在如新的CLI工具、Web浏览器的核心引擎、高性能网络代理等特定“利基市场(niche market)”,其中内存安全和C++级的性能同时极端重要,且GC的存在本身就是一种负担,那么Rust就可能是一个极其有效和强大的选择。
总而言之,“最好的语言”并不存在,只存在“对特定问题最合适的工具”。所有语言都有其优点和随之而来的明确成本。因此,将某一特定语言视为所有问题的解决方案,并贬低其他替代方案价值的观点,可能与“选择最适合给定问题的工具”这一核心工程原则相冲突。
4. 对“所有权”模型的再评估与设计哲学
4.1 所有权概念的起源:C++的RAII模式与智能指针
要理解Rust的核心特性——所有权(ownership)模型,我们必须首先回顾其概念诞生的历史背景,特别是C/C++语言中资源管理方式的演进。
C语言的手动内存管理及其局限
C语言通过malloc()
和free()
函数,赋予了程序员对动态内存的直接控制权。这种设计提供了高度的灵活性和性能,但也将其所有已分配内存,都必须在正确的时机、且仅被释放一次的责任,完全交给了程序员。
这种手动管理模型在程序员发生失误时,可能导致以下顽固的内存错误:
- 内存泄漏 (memory leak): 已分配的内存未被释放,导致可用内存逐渐减少。
- 二次释放 (double free): 对已释放的内存再次进行释放,从而破坏内存管理器的状态。
- 释放后使用 (use-after-free): 访问已被释放的内存区域,导致数据损坏或安全漏洞。
这些问题揭示了仅依赖程序员个人责任的内存管理方式的内在局限,C++因此开始探索一种系统性的解决方案。
C++的演进:RAII模式与智能指针
C++为了将资源管理的责任从程序员个人,转移到语言的对象生命周期管理规则上,引入了RAII(Resource Acquisition Is Initialization)模式。RAII是一种在对象的构造函数中获取资源,并在析构函数中自动释放资源的范式。由于C++编译器保证在对象离开作用域时(无论是正常结束还是因异常退出),其析构函数一定会被调用,因此可以从根本上防止因程序员失误导致的资源释放遗漏。
将RAII模式应用于动态内存管理的最典型案例,便是智能指针(smart pointers)。特别是自C++11标准引入的智能指针,与Rust的所有权模型在哲学上具有显著的相似性。
std::unique_ptr
(唯一所有权): 表示对某一资源的独占所有权。其禁止复制、只允许所有权“移动(move)”的概念,与Rust的基本所有权模型和移动语义(move semantics)直接对应。std::shared_ptr
(共享所有权): 通过引用计数(reference counting),提供了一种让多个指针安全地共同拥有同一资源的方式。这是Rust中Rc<T>
和Arc<T>
概念的基础。
由此可见,C++通过RAII和智能指针,已经确立了“资源所有权”的概念,并为其提供了一套系统的解决方案。
4.2 Rust的独创性:并非“概念的发明”,而是“编译器的强制”
前一节确认了Rust的所有权(ownership)概念深植于C++的RAII模式和智能指针。那么问题随之而来:Rust的独创性体现在何处?结论是,Rust的工程学贡献,并非在于概念本身的“发明”,而在于它将既有的所有权原则,在语言层面进行“强制”的方式。
从“可选模式”到“强制规则”的转变
在C++中,使用std::unique_ptr
这类智能指针,是一种提升内存安全的有效设计模式(design pattern),但它终究是开发者的“可选项”。开发者随时可以不遵循此模式而使用裸指针(raw pointer),编译器并不会阻止。也就是说,确保安全的最终责任,依赖于开发者的纪律和惯例。
相比之下,Rust将所有权规则,从一种可选的模式,转变成了内建于语言类型系统中的强制性规则(mandatory rule)。所有值都受此规则支配,并由一个名为借用检查器(borrow checker)的静态分析工具在编译时验证其是否被遵守。只要不使用unsafe
块,任何违反规则的行为都将导致编译错误,而非简单的警告,从而从根本上阻止了不合规程序的生成。
这种将安全保证的主体从“开发者的纪律”转移到“编译器的静态分析”的设计,与C++构成了根本性的差异,并塑造了Rust的核心特征。
从资深开发者视角看的权衡
这种“编译器的强制”特性,从资深C/C++开发者的角度看,具有实用性与约束性的双重面貌。
资深的C/C++开发者很容易认识到,Rust的所有权规则,与他们为防止失误而长期遵守的最佳实践(best practice)不谋而合。
- Rust的移动(move)语义,与C++中使用
std::unique_ptr
和std::move
的所有权转移模式类似。 - Rust的不可变引用(
&T
)和可变引用(&mut T
),与C++中为保证数据不变性而使用const T&
或为防止并发修改的设计原则,在精神上一脉相承。
从这个角度看,Rust可被评价为一个有用的工具,它将以往“隐性的纪律”通过编译器进行了显式的强制。
然而,正是这种严格的强制性有时也会成为一种局限。在实现复杂数据结构或进行极致性能优化时,资深开发者可能能够运用一些超越借用检查器分析能力的、但逻辑上安全的内存管理模式。由于借用检查器无法证明所有有效的程序,开发者可能会遇到一些逻辑上安全的代码,仅仅因为“编译器无法证明其安全性”而被拒绝的情况。
总而言之,Rust的所有权模型,通过普遍的规则强制,极大地提升了代码的平均安全水平,是一项重要的工程成就。同时,由于其设计哲学将既定规则置于专家判断之上,它也内含着在特定情况下会约束开发灵活性的权衡(trade-off)。
4.3 设计哲学比较:所有权模型与契约式设计
为保证软件的正确性(correctness),编程语言会采纳不同的设计哲学。Rust所使用的所有权(ownership)与借用(borrowing)模型,侧重于在编译时自动防止特定类型的错误。而像Ada/SPARK这类语言所采用的契约式设计(design by contract),则是一种由开发者明确声明逻辑“契约”,再由工具进行验证的方式。
为了分析这两种哲学的差异及各自的工程权衡,我们将以计算机科学中的基础数据结构——双向链表(doubly-linked list)的实现作为案例研究。
1. 方法一:Rust的所有权模型
双向链表的每个节点(Node)都相互引用其前驱和后继节点。这种在其他语言中通过指针或引用能较直观实现的结构,与Rust的基本规则直接冲突。因为Rust的所有权系统,默认不允许循环引用(reference cycle)或对单一数据的多重可变引用。
因此,最直观的节点定义形式会被借用检查器(borrow checker)判为编译错误。
// 无法通过编译的直观代码
struct Node<'a> {
value: i32,
prev: Option<&'a Node<'a>>,
next: Option<&'a Node<'a>>,
}
要在“安全(safe)”的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>>>>,
}
- 分析:这种方法的一大优势是,编译器能自动防止如数据竞争(data race)这类特定并发问题。所有权规则默认强制了最安全的状态,而对于像双向链表这样需要复杂共享状态的情况,它引导开发者通过使用
Rc
、RefCell
等来显式地选择(opt-in)这种复杂性。此设计哲学的主要成本,便是在此过程中产生的认知成本(cognitive cost)和代码的冗长(verbosity)。开发者的焦点可能更多地集中在如何满足编译器的规则,而非问题本身的逻辑结构上。
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通过在运行时检查如空指针(null access)解引用这类错误,并抛出Constraint_Error
异常来确保安全。
更进一步,Ada的子集SPARK提供了一种通过契约式设计,在编译时数学地证明运行时错误不存在的方法。开发者为过程(procedure)或函数标注前置条件(precondition, Pre
)和后置条件(postcondition, Post
),然后由静态分析工具来验证代码是否始终满足这些契约。
-- 通过SPARK契约证明安全的示例
procedure Process_Node (Item : in Node_Access)
with Pre => Item /= null; -- 标注契约:'Item不为null'
- 分析:这种方法通过与C/C++类似的指针模型,为开发者提供了更直接地表达数据结构的灵活性。其安全性通过运行时检查,或是由开发者亲自编写的明确契约及静态分析工具的证明来保障。此设计哲学的主要成本,是开发者必须考虑所有潜在的错误路径,并将其编写为形式化契约的责任与努力。如果契约被遗漏或编写错误,安全保证便可能不完整,这蕴含着与依赖自动化规则的方式不同类型的风险。
3. 设计哲学比较与结论
这两种方法,是将确保软件正确性的责任与成本,分配给了不同的主体和不同的阶段。
维度 | Rust | Ada/SPARK |
---|---|---|
安全保障主体 | 编译器 (隐式规则的自动强制) | 开发者 + 工具 (显式契约的编写与静态证明) |
基本范式 | 默认限制 (restrictive by default), 选择性放宽 (opt-in complexity) | 默认允许 (permissive by default), 通过契约施加约束 (opt-in safety proof) |
主要成本 | 实现特定模式时的高认知开销 (cognitive overhead) 与代码复杂性 | 为所有交互编写形式化规约 (formal specification) 的负担 |
主要优势 | 自动防止如数据竞争等特定错误类别 | 直接表达开发者的设计意图,并可证明广泛的逻辑属性 |
总而言之,我们很难用“革新”或“缺陷”这种二元对立的视角来评价Rust的所有权模型。它是一种具有鲜明优点及其相应成本的、独特的设计哲学。这种哲学在预防特定类型bug方面极为有效,但其过程也内含着要求开发者支付高昂学习成本、并以非直观方式解决特定问题的权衡。一门语言的适用性,取决于待解决问题的类型、团队的能力,以及项目所优先考虑的价值(例如:自动化的安全保证 vs. 设计的灵活性)。
第三部分:生态系统的现实与结构性成本
在第三部分中,我们将分析Rust生态系统所面临的现实挑战及其背后的结构性成本。在评估Rust的开发者体验(DX)、“零成本抽象”原则以及实际产业应用中的制约因素时,我们必须认识到,所面临的问题按其性质可被划分为以下两大类,这一点至关重要。
- 生态系统的“成熟度”问题 (Problems of ‘Maturity’):这类问题,如库的缺失、部分工具的不稳定性、文档不完善等,是能够随着时间的推移和社区努力的积累而自然解决或缓解的。这属于所有成长中技术生态系统都会共同经历的成熟度问题。
- 设计中内蕴的“根本性权衡” (Inherent ‘Trade-offs’ in Design):这类问题源于语言为实现其核心价值(如:运行时性能、无GC内存安全)而有意牺牲其他价值(如:学习易用性、编译速度、特定模式实现的灵活性)的结果。这并非“缺陷”,而是“选择”,因此即便时间流逝,其本质也难以消除。
本部分将基于此分析框架,对Rust的诸多技术挑战进行归类,并明确评估其问题性质。
5. “开发者体验(DX)”的成就与成本
5.1 借用检查器、学习曲线与生产率的权衡
实现Rust安全模型的关键技术,是在编译时静态强制执行所有权(ownership)、借用(borrowing)和生命周期(lifetimes)规则的借用检查器(borrow checker)。这一机制是保障内存安全的重要工程学成就,但其严苛性也成为导致与开发生产率之间产生权衡(trade-off)的主要原因。对于习惯了其他编程范式的开发者而言,Rust的方式要求他们从根本上重构既有的思维模式,这直接导致了其陡峭的学习曲线。
学习难度的技术根源
开发过程中所遇到的主要困难,源于借用检查器所强制执行的以下技术特性:
- 所有权与借用模型的认知负担:对所有值应用单一所有者规则,并在访问数据时严格遵守不可变(immutable)或可变(mutable)借用规则,这要求相当大的认知负担。因此,开发者可能陷入一种境地:他们投入更多精力去满足编译器的规则,而非实现问题的核心逻辑。
- 生命周期标注的复杂性:在编译器无法自动推断引用有效性的复杂场景中,开发者必须手动标注生命周期参数(
'a
)。这是一项要求额外抽象思维的工作,其目的仅是为了让代码通过编译器的静态分析,与解决问题的本质相去甚远。 - 特定设计模式的实现困难:借用检查器严格的分析模型,使得像双向链表或需要循环引用的图结构这类计算机科学中的基础数据结构,极难仅用“安全(safe)”的Rust代码进行直观实现。这恰好说明了借用检查器的分析模型在表达所有有效程序方面存在局限。
对生产率的影响与话语的形成
这些技术挑战对实际商业项目的生产率构成了直接影响。当团队中有新开发者加入时,可能会产生相对较长的适应期和高昂的培训成本(初期生产率下降);一个看似简单的功能实现,可能因意料之外的编译错误而延迟,从而降低了项目进度管理的可预测性。在将开发者时间视为重要资源的商业环境中,这无疑构成了明确的成本(cost)和风险(risk)。
这种学习曲线并非源于语言的不成熟,而是为了达成“无性能损耗的安全”这一目标而有意选择的、设计上内蕴的权衡。然而有趣的是,在部分与Rust相关的在线讨论中,我们观察到一种话语倾向,它试图将这种学习上的困难,重新诠释为一种积极的价值,而非语言的技术局限或生产率的阻碍因素。高学习难度有时被视为“帮助开发者成长的积极过程”或是“衡量专业性的一把标尺”。
这种视角营造了一种氛围,使得对于学习过程中所遇困难的建设性批评,容易被归咎为个人的“努力不足”或“对既有语言的理解不够”。有批评认为,这最终会提高新开发者的入门门槛,并可能抑制对改善语言和编译器易用性的健康讨论。这一点也与将在8.4节及附录中分析的特定论证谬误模式相关联。
5.2 技术选择的过度泛化倾向与工程权衡
在技术史上,当一种强大而新颖的工具出现时,人们倾向于将其视为解决所有问题的万能钥匙,这种现象屡见不鲜。这正是“工具定律(law of the instrument)”或“锤子综合症”所描述的一种普遍现象,其格言是“手持锤子的人,看所有问题都像钉子”。这种倾向与其说是特定技术社区的独有特质,不如更准确地理解为在技术采纳过程中普遍出现的社会心理动态。
Rust语言为分析此普遍现象提供了一个重要的案例研究。该语言所提供的“内存安全”这一明确而强大的价值,以及为掌握它所要求的高昂学习成本,促使开发者对该技术进行大量投资。这种投资继而会产生一种最大化该技术效用价值的动机,表现为试图将其应用范围,从其能发挥核心优势的特定“利基市场”,扩展到更广阔的领域。
本节将从两个方面分析这种“过度泛化”的倾向,在部分与Rust相关的讨论中是如何体现的。首先,我们将检视在评价其他编程语言时,Rust的核心价值(如:无GC,运行时性能)是如何被用作一种排他性的评价标准。其次,我们将通过大多数常规Web应用开发这类具体案例,探讨在考量问题特性和商业约束条件时,工程权衡分析是如何被忽视的。
与其他技术比较方式中体现的偏见
这种技术选择的过度泛化倾向,在部分讨论中,伴随着与其它编程语言或框架进行比较时的特定偏见。
Rust所拥有的明确优势,即“无垃圾回收器(GC)的内存安全”和“高运行时性能”,常常被用作评价技术的唯一或最重要标准。在此框架下,其他语言可能被以下方式贬低:
- C/C++:内存安全性的缺失这一单一弱点,成为了压倒该语言所有其他方面(如庞大的生态系统、硬件控制能力等)的评价依据。
- Go, Java, C#:GC的存在本身被指为性能下降的元凶,而这些语言所拥有的高开发生产率或成熟的企业级生态系统的价值,则被相对低估。
- Python, JavaScript:静态类型系统的缺失被作为“不安全”的证据,而这些语言在快速原型开发和开发速度上的优势,则被视为次要因素。
成熟的工程学评价,必须综合考量各种权衡(trade-off)。仅选择性地强调特定标准来评判技术优劣的方式,在客观评价每种技术于不同问题领域中的适用性时,存在局限。
案例研究:Web后端开发中的“过度泛化”
此类过度泛化的一个典型案例,是“所有Web后端开发都应使用Rust”的主张。
Rust在某些对性能和低延迟有极端要求的特定Web服务领域,如高性能API网关、实时通信服务器等,可以成为一个出色的选择,这一点是合理的。内存安全也是提升服务器稳定性的一个重要因素。
然而,这类主张可能犯下一种错误,即将Rust展现出优势的特定领域的需求,过度泛化到了远为广阔和多样的“所有Web后端”领域。对于绝大多数常规Web应用(如:SaaS、内部管理系统、电商平台)的开发而言,以下这些商业和工程因素,通常比原始性能更为重要:
- 开发速度与产品上市时间(time-to-market)
- 生态系统的成熟度(如:认证、支付、ORM等库的完善程度)
- 新员工的学习易用性及开发者人才库的规模
在这些衡量标准下,像Go、C#/.NET、Java/Spring、Python/Django等拥有成熟生态系统的既有语言,可能比Rust是更合理、更经济的选择。不考虑问题特性和商业约束,而主张应用特定技术的做法,是一种缺乏工程权衡分析的思维方式。
5.3 异步编程模型的复杂性与工程权衡
Rust的异步编程模型(async
/await
)基于“零成本抽象”原则,其设计目标是在没有垃圾回收器或笨重的绿色线程(Green Thread)的情况下,实现高运行时性能。这在需要高效利用操作系统线程的系统编程领域,是一个重要的设计目标。
然而,这一设计上的选择,也伴随着开发者必须承受的明确成本,即概念上的复杂性和调试的困难。
技术复杂性的根源
Rust的async
/await
通过编译器将异步代码转换为复杂的状态机(state machine)来工作。在此过程中,可能会产生一种在内存中包含对自身引用的“自引用结构体(self-referential struct)”。为了保证这类结构体的内存地址稳定,Rust引入了一种名为Pin<T>
的特殊指针类型。
Pin<T>
及其相关的生成器(Generator)等,是其他主流语言中罕见的高度抽象概念,理解其工作原理需要相当的学习投入。这种复杂性可被视为一种“泄漏的抽象(leaky abstraction)”。即便是引领Rust异步生态的核心开发者们,也在博客或演讲中承认这些概念的学习曲线陡峭,并持续提出对其易用性进行改善的必要性。7
对开发体验的实际影响
async
模型内部的复杂性,在实际的开发和维护过程中,会导致以下困难:
- 调试难度增加:当
async
代码发生错误时,其输出的调用栈(stack trace)常常充满了异步运行时的内部函数和由编译器生成的、意义不明的状态机调用,这使得追踪错误的根本原因变得困难。此外,与同步代码不同,异步函数中的局部变量被捕获在状态机对象内部,这使得通过调试器进行状态追踪变得异常棘手。 - 成本转移(Cost Shifting):结果是,Rust的异步模型,在最小化运行时的CPU和内存占用(机器时间)的同时,将其成本转移给了开发者的学习时间和调试困难(开发者时间),这是一种设计上的权衡。
与替代模型的比较分析
当与Go语言的协程(Goroutine)这类替代性异步模型进行比较时,这种权衡关系会更加清晰。协程通过由语言运行时管理的轻量级线程(green thread),为开发者提供了一个远为简单和直观的并发编程模型。
维度 | Rust async /await |
Go 协程 (Goroutine) |
---|---|---|
设计目标 | 零运行时开销 | 开发生产率与简洁性 |
运行时成本 | 最小化 | 因调度器、GC存在少量成本 |
学习曲线 | 高 (需理解Pin 等概念) |
极低 (go 关键字) |
调试 | 困难 (复杂的调用栈) | 容易 (清晰的调用栈) |
当然,在CPU密集型(CPU-bound)任务中,Rust模型的性能优势可能很明显。但在网络延迟或数据库响应速度是瓶颈的常规I/O密集型(I/O-bound)工作环境中,开发者可能会觉得,Go模型所付出的少量运行时成本,远小于Rust模型所要求的开发与调试的复杂性成本。
在部分Rust社区中,有时会观察到一种倾向,即以Go模型“非零成本”为由对其进行贬低。但这可能是一种偏颇的视角,它仅以“运行时性能”为唯一标尺来评价技术,而忽视了“开发生产率”和“维护易用性”等其他重要的工程学价值。
5.4 对显式错误处理模型(Result<T, E>
)的实用性再思考
Rust采纳了通过Result<T, E>
枚举、模式匹配以及?
运算符,在编译时强制进行错误处理的显式错误处理模型。该模型被誉为是防止错误处理遗漏的强大手段。本节将通过与替代性错误处理方式的比较、概念的历史溯源以及分析其实际使用成本,来多维度地再思考该模型的实用性。
1. 与替代模型的比较:try-catch
异常处理
在讨论Rust的Result
模型时,基于try-catch
的异常处理模型常因其不可预测的控制流而受到批评。然而,成熟的异常处理机制拥有其独特的工程学价值:
- 关注点分离(separation of concerns):通过在
try
块中编写正常逻辑,在catch
块中编写异常处理逻辑,可以分离关注点,提升代码可读性。控制流能从错误发生点即刻传递到处理点,避免了需要跨越多个函数层级手动传播错误(return Err(...)
)的冗长。 - 编译时检查:“无法预知会发生何种异常”的批评并非适用于所有情况。例如,Java的“受检异常(Checked Exception)”要求函数在其签名中明确声明可能抛出的异常,并由编译器强制其被处理。这是以不同于
Result
类型的方式,达成防止错误遗漏这一目标的案例。 - 系统恢复力(resilience):现代异常处理系统通过错误记录(logging)、资源清理(
finally
)以及错误恢复逻辑,在防止程序异常中止、维持服务稳定运行方面扮演着重要角色。
2. 概念的历史溯源:函数式编程
通过Result
和Option
进行显式的错误与状态处理,并非Rust的独创。它成功地借鉴了其有效性早已被证明的概念。这一思想的根源在于函数式编程(functional programming)阵营。
Haskell的Maybe a
、Either a b
类型,或OCaml、F#等ML系语言中的和类型(Sum Type),早在几十年前就开始使用类型系统来表达值的缺失或错误状态,并强制编译器处理所有情况。
因此,一个更准确的评价是,Rust的贡献不在于“发明”了这一概念,而在于结合系统编程语言的上下文对其进行了“再诠释”,并通过?
运算符等语法便利性,使其得到了“普及化”。
3. 实用成本:错误类型转换的冗长性(verbosity)
?
运算符在传播同一种错误类型的场景下极为便利,但在使用多种外部库的真实应用中,其局限性便会显现。不同的库会返回其各自独有的错误类型(例如 std::io::Error
, sqlx::Error
),开发者必须反复编写将这些不同错误转换为应用统一错误类型的样板代码(boilerplate code)。
// 将多种不同错误转换为单一应用错误的示例
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
等外部库被广泛使用。然而,在一个生态系统中,为了某个特定功能(此处是灵活的错误处理)而使用外部库成为一种事实上的标准,这一现象本身,也暗示了仅靠语言的基本功能在开发实用应用时存在不便。
5.5 Rust生态系统的质量成熟度挑战与社区话语分析
Rust的官方包管理器Cargo和中央仓库Crates.io,在其快速采纳和成长过程中扮演了核心角色。这带来了库(即crate)共享的数量上的激增。然而,在这数量增长的背后,是确保生产环境中稳定性和可靠性时所面临的质量成熟度挑战。本节将分析Rust生态所面临的主要质量挑战,并审视社区在面对这些问题时所呈现的典型话语结构。
1. crate生态质量成熟度的主要挑战
在生产环境中使用Rust的开发者,可能会在库生态方面面临以下现实问题:
- API稳定性不足:相当数量的crate长期维持在语义化版本(semantic versioning)1.0.0以下的
0.x
版本。这意味着其公共API尚未稳定,随时可能发生不保证向后兼容的破坏性变更(breaking change)。在有生产依赖的项目中,这会成为增加潜在维护成本和风险的因素。 - 文档质量参差不齐:尽管
cargo doc
能够生成标准化的文档,但实际crate的文档水平差异巨大。一些crate除了API列表外,缺乏具体的使用示例或设计哲学说明,导致开发者为有效使用该库而必须亲自分析源代码。这可能与使用库以提升生产率的初衷相悖。 - 维护可持续性问题:作为许多开源生态的共同问题,即便是核心的crate,也常常由少数志愿者维护。如果核心维护者因个人原因停止项目,那么对安全漏洞或主要bug的后续响应,便存在长期延迟的风险。这可能会影响到依赖该crate的整个生态的稳定性。
2. 对生态问题的批评及观察到的回应模式分析
当对生态的质量问题提出批评时,在某些线上论坛等公开讨论空间中,我们常观察到一些特定的话语模式,它们倾向于将讨论引向与技术问题本质不同的方向。
- 通过“鼓励参与”来转移责任:“欢迎提交PR(Pull Requests are welcome)”或“如果你需要,就自己贡献”这类回应,是鼓励开源核心价值——自愿参与——的积极表达。但当它被用作对库的缺陷或文档不足等正当批评的回应时,它也可能执行一种修辞功能,即将解决问题的责任转移给最初提出问题的人。考虑到并非所有用户都具备修改库的专业能力或时间,这种回应可能会抑制建设性反馈的循环。
- 成功案例的代表性问题与统计学视角:在回应关于生态整体质量成熟度的批评时,一种常见的反驳是举出像
tokio
、serde
这样被极成功管理的少数核心crate作为例证。这些成功案例无疑展示了Rust生态的潜力及其能达到的高质量水平。然而,这种论证方式需要从“样本代表性(representativeness of the sample)”的角度进行批判性审视。因为少数异常成功的案例,很难说能代表由成千上万个库构成的整个生态的平均成熟度,或是普通开发者所面对的现实。这并非仅为指出逻辑谬误,而是提出了一个工程学和统计学上的问题:特定的样本(成功案例)是否足以描述整个总体(生态系统)的特性。这种方法可能导致人们将注意力局限于少数顶尖案例,从而高估了生态的现状,而非全面审视各个库所面临的现实问题。
5.6 开发工具链的技术挑战与生产率
Rust语言的开发者体验,在提供强大功能的同时,也伴随着一些技术挑战。这些挑战尤其可能对大型项目的开发生产率产生实际影响。本节将从编译器的资源使用、IDE集成与调试环境,以及构建系统的灵活性等方面,分析其主要问题。
1. 编译器的资源占用及其影响
Rust编译器(rustc
)在编译过程中,倾向于需要大量的时间和内存资源。这源于语言的根本设计,如为实现“零成本抽象(ZCA)”而采用的单态化(monomorphization)策略,以及对LLVM后端的依赖等。
- 编译时间:单态化会为每种泛型类型生成独立的代码,这增加了编译器(特别是LLVM后端)需要处理和优化的代码量。因此,它延长了“修改代码→编译→测试”这一开发反馈循环(feedback loop),尤其当项目规模增大时,这会成为阻碍开发者生产率的因素。尽管
cargo check
等工具提供了快速的语法检查,但完整的构建和测试仍然可能耗时良久。 - 内存使用:编译过程中的高内存占用,可能在资源受限的开发环境(如个人笔记本、低规格的CI/CD构建服务器)中引发问题。在大型项目中,编译器进程甚至可能超出系统的可用内存,被操作系统的OOM(Out of Memory)Killer强制终止。这损害了开发体验的稳定性。
不过,需要指出的是,这些成本并非一成不变。Rust项目和社区已将编译时间视为核心的改进课题,并持续为此努力。例如,为提升调试构建速度而开发的Cranelift
后端,以及增强rustc
编译器自身并行处理能力的尝试等,都表明这种工程权衡并非静态问题,而是在被动态地管理和改善。
2. IDE集成与调试环境
IDE(集成开发环境)支持和调试环境,与其它成熟语言的生态相比,尚有改善空间。代表性的语言服务器rust-analyzer
虽提供了实时分析等强大功能,但在处理复杂的宏或类型系统时,偶尔会给出不准确的诊断,或占用大量系统资源,表现出不稳定的一面。
调试环境也存在困难。尽管使用的是LLDB或GDB等标准调试器,但像Vec<T>
、Option<T>
这类Rust的抽象类型,在调试器中其内部结构会被直接暴露,难以进行直观的状态确认。特别是async
/await
代码,由于被编译器转换为复杂的状态机,使用传统的调试方式极难追踪错误的根本原因。
3. 构建系统(Cargo)的灵活性
Rust的官方构建系统Cargo,基于“约定优于配置(convention over configuration)”的哲学,通过标准化的项目管理和便捷的依赖解析,提供了高生产率。这是Cargo的明确优势。
然而,当项目的需求超出标准范围时,这种优势也可能转变为僵化。在需要复杂的代码生成、与外部库进行特殊集成等非标准构建流程时,仅靠build.rs
脚本可能难以灵活应对。此外,在大型的单一代码库(monorepo)环境中,特性标志(feature flags)的组合可能变得异常复杂,导致依赖管理本身成为一种新的维护成本。这对于需要应对多样化构建场景的大型工业环境而言,可能成为一个制约因素。
本章小结
本章所分析的Rust开发者体验(DX)相关挑战,可被归入前述的两个范畴来理解。
首先,借用检查器的陡峭学习曲线、async
模型的概念复杂性,以及Result<T, E>
类型在显式错误处理中引发的部分冗长,这些都属于为实现“无性能损耗的安全”这一核心价值,而在语言设计阶段被有意选择的“内蕴的权衡”。它们是Rust固有的特性,即便生态成熟也难以完全消除。
其次,部分crate的API不稳定性与文档缺失,以及rust-analyzer
或调试器等开发工具链的部分不稳定性,则更具“成熟度问题”的性质。这类问题能够随着时间的推移和社区努力的积累而逐步得到解决。
因此,在评价Rust的开发者体验时,清晰地区分这两种结构上不同的成本类型,是至关重要的。
6. “零成本抽象”的真实成本分析
6.1 成本转移机制:单态化(Monomorphization)的角色
Rust的核心设计原则之一是“零成本抽象(Zero-Cost Abstractions, ZCA)”。它意指开发者在使用泛型(Generics)、迭代器(Iterator)等高级别抽象功能时,不应因此导致程序的运行时性能下降。
该原则并非Rust独创,而是深植于C++的设计哲学。C++之父Bjarne Stroustrup提出的“不为你未使用的东西付出代价”原则,与ZCA的本质相同。C++早已通过模板(Templates)等功能,实现了在编译时生成代码以消除运行时开销的方式。
Rust继承了这一ZCA哲学,并如同其继承所有权模型一样,将其与所有权及借用检查器相结合,向着同时保证内存安全的方向发展。然而,“零成本”一词仅指“零运行时成本”,并不意味着实现抽象所需的成本本身不存在。将Rust的ZCA理解为一种成本转移(cost-shifting)机制会更为准确:它以牺牲开发周期的其他环节为代价,来确保运行时性能。
这种成本转移的核心,在于一种名为单态化(monomorphization)的编译策略。它指在编译像Vec<T>
这样的泛型代码时,会为代码中所有使用到的具体类型(如Vec<i32>
, Vec<String>
),分别生成特化、独立的代码。这一策略消除了在运行时进行类型检查或虚函数调用等间接开销,从而保证了高执行速度,但也产生了两个主要成本:
- 增加编译时间:编译器需要为每个使用到的泛型类型复制一份代码,并对每一份进行单独优化。这增加了编译器(特别是LLVM后端)需要处理的代码量,是导致整体编译时间延长的主要原因。
- 增大二进制文件体积:所有生成的特化代码,都会被完整地包含在最终的可执行文件中。这导致逻辑上相同的代码,以多个副本的形式存在,从而增大了最终二进制文件的体积。当与静态链接方式结合时,这一点尤为突出。
作为单态化的替代方案,Rust也提供了利用特质对象(&dyn Trait
)的动态分发(dynamic dispatch)方式。这种方式不复制代码,而是生成一个单一函数,并在运行时查找并调用所需的实现。它以承受少量运行时成本为代价,换取了缩短编译时间和减小二进制体积的实用性权衡。
总而言之,Rust的“零成本抽象”是其将运行时性能置于首位的设计哲学的产物。然而,在此过程中产生的编译时间和二进制体积增加的成本,对开发生产率和部署环境都构成了实际影响。在评价ZCA原则时,这种成本转移的 측面是必须一并考量的重要因素。这是一种为达成“运行时零成本”目标,而支付“编译时间”和“二进制体积”作为代价的、明确的设计权衡。
6.2 二进制文件体积分析:设计原则对应用领域的影响
Rust程序的可执行文件(binary)体积,相较于用C/C++编写的类似功能程序,有偏大的倾向。在Rust被作为C/C++主要替代方案来讨论的资源受限的系统编程领域,这是一个重要的考量点。本节将分析此现象的技术原因,并通过具体案例比较来检视其影响。
1. 技术原因:ABI不稳定性与静态链接
Rust二进制体积增大的一个根本原因在于,其标准库(libstd
)的ABI(Application Binary Interface)不保持稳定这一设计特点。C语言基于数十年稳定的libc
ABI,支持多个程序共享使用系统中已安装的共享库,即动态链接(dynamic linking)。因此,C程序的可执行文件只需包含其独有代码,从而保持较小的体积。
相比之下,Rust为了语言和库的快速改进与演进,选择了不将libstd
的ABI稳定化,以便能自由地更改其内部实现。这是在“稳定兼容性”与“快速演进”之间,优先选择了后者的设计决策。此选择的结果是,难以保证版本间兼容性的动态链接被放弃,取而代之的是将所有需要的库代码都包含进可执行文件的静态链接(static linking),成为了默认方式。因此,即便是简单的程序,libstd
的相关功能也会被全部包含进二进制文件,导致体积增大。
2. 案例研究:CLI工具与核心工具集的比较
这种设计的影响,可以通过实际程序的体积比较得到验证。
案例1:grep
与ripgrep
ripgrep
是一个用Rust编写的高性能文本搜索工具,以其优于基于C语言的grep
的性能而闻名。然而,在常规Linux系统中,动态链接的grep
体积仅为数十KB,而静态链接的ripgrep
则高达数MB。这在部署单个应用时,提供了依赖管理上的便利,但在旨在全面替代操作系统基础工具的场景下,则可能成为总容量增加的负担。
案例2:BusyBox
与uutils
在资源极其受限的嵌入式Linux环境中,将ls
、cat
等大量命令集于单一二进制文件的BusyBox
被广泛使用。用C语言编写的BusyBox
整体体积通常小于1MB,非常小巧。相比之下,以类似目的用Rust开发的uutils
,其体积则达到数MB。当然,具体大小会随项目版本和编译环境而变,但这种趋势性差异,源于两种语言标准库设计和默认构建方式差异所导致的结构性结果。下表是基于Alpine Linux软件包的比较。
表6.2:主要核心工具集实现的软件包大小比较 (基于 Alpine Linux v3.22)8
软件包 | 语言 | 结构 | 安装大小 (约) |
---|---|---|---|
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时,不进行栈展开(unwinding),而是立即中止程序,从而移除相关代码和元数据。这能减小体积,但也省略了资源的安全清理过程。 - 排除标准库 (
no_std
):不使用提供堆内存分配、线程、文件I/O等依赖操作系统功能的libstd
。这能革命性地减小体积,但也带来了必须自行实现或依赖外部crate来获得Vec<T>
、String
等核心数据结构和功能的限制。
由此可见,要在Rust中实现C/C++级别的微小二进制文件,就必须有意地禁用语言默认提供的丰富功能和部分安全保障。这暗示了Rust的默认设计哲学,更侧重于功能的丰富性和运行时性能,而非微小的二进制体积。
本章小结
“零成本抽象”原则及其实现方式——单态化——所导致的编译时间和二进制体积增加,是展现Rust设计哲学的典型案例。
这些成本并非源于技术不成熟的“成熟度问题”,而是为了确保“运行时性能”这一最高价值,而有意牺牲“开发时间”和“部署体积”等其他资源的、明确的“内蕴的权衡”。这清晰地揭示了工程学的根本原则:“成本不会消失,只会被转移到别处”。因此,开发者必须理解“零成本”一词背后这种成本转移的机制,并审慎评估自身项目的约束条件(如:快速的编译、微小的体积)与Rust的设计哲学是否契合。
7. 产业应用的现实制约
7.1 在嵌入式与内核环境中的应用与技术制约
Rust被作为C/C++替代方案来评估的一个主要领域,是嵌入式系统和操作系统内核开发。然而,在这两个领域应用Rust,存在一些重要的技术制约。
首先,在无操作系统、直接控制硬件的裸机(bare-metal)及小型微控制器环境中,二进制文件体积是核心制约因素。Rust的标准库(libstd
)虽功能丰富,但若将其静态链接进程序,可执行文件体积可能高达数MB。这对于存储空间仅为数KB的系统而言,难以应用。作为解决方案的no_std
环境,虽通过排除标准库减小了体积,但其代价是限制了堆内存分配、线程、标准数据结构等核心功能的使用。开发者必须自行实现或依赖外部crate,从而增加了开发复杂性。尽管如此,部分开发者也认为,编译时强大的安全保证,足以抵消这些成本,是一种合理的工程选择。
其次,在将Rust集成到像Linux内核这样既有的C语言项目中时,会产生对C ABI的强依赖。“Rust for Linux”项目虽证实了Rust在内核中使用的可能性,但目前用Rust编写的模块必须遵循由C语言定义的数据结构和调用约定(calling convention)。在此过程中,unsafe
关键字的使用变得频繁,这意味着Rust的编译时安全保证模型被部分地绕过了。
为了定量分析Linux内核的实际集成现状,我们使用cloc
v2.04工具分析了由kernel.org发布的Linux内核v6.15.5(截至2025年7月9日)的源代码9。 分析结果显示,在排除注释和空行后的纯代码行(SLOC)总计为28,790,641行,其中Rust代码为14,194行,约占总量的0.05%。
这一数字仅反映了特定时间点的现状。由于Rust在内核中的集成是一个持续进行中的项目,该比例未来可能发生变化。尽管如此,此数据在揭示截至2025年中期,Rust在内核庞大的C语言代码库中所占的相对规模,以及其仍处于非常初期的集成现状方面,具有客观意义。当然,代码的量化比重并不直接代表其质量上的重要性或技术影响力。从质上看,当前已包含的代码内容,其作用主要集中于为编写驱动程序构建基础架构。同时,这种基于客观数据的批评,在特定技术话语中是如何被接受和辩护的,将在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行。部分语言已省略。
7.2 任务关键系统与国际标准的缺失
在航空、国防、医疗等要求高可靠性的任务关键(Mission-Critical)系统领域,选择语言时,除了技术性能,产业标准和生态成熟度是重要的评判标准。
这些领域为保证软件的稳定性和可预测性,常常要求遵循国际标准(如:ISO/IEC)。一门标准化的语言,其规约是固定的,便于长期维护,并能成为孕育一个由多家供应商提供兼容编译器、静态分析工具、认证支持服务等的商业生态的基础。C、C++、Ada等语言都具备这样的标准化流程和成熟的供应商生态。
然而,Rust并非一门被制定为国际标准的语言,并且为了快速发展,它采纳了一种灵活更改语言规约的模型。这种“快速演进”模型虽有利于短期的功能改进,但与对变化极其保守、将长期稳定性置于首位的任务关键领域的需求,可能存在冲突。结果是,相关的合规与认证流程变得复杂,也难以获得专业的商业供应商支持,这构成了其正式进入该领域的结构性障碍。
7.3 在一般产业界采纳的现实障碍
Rust要超越特定领域,向一般产业界全面扩散,存在以下现实障碍:
- 人才供给与培训成本:熟练Rust开发者的人才库,相较于Java、C#、Python等主流语言,仍然有限。这对企业而言,意味着招聘困难和高昂的人力成本。此外,要将既有开发者转型至Rust,必须承受对其所有权模型等独特概念的高昂学习成本和初期生产率下降的阶段。
- 企业级生态的成熟度:在大型企业应用开发中必不可少的ORM(对象关系映射)框架、云服务SDK、认证/授权库等生态,在某些领域其成熟度尚不及Java或.NET等。这使得重视开发速度和稳定性的企业环境,在采纳时会感到犹豫。
- 遗留系统集成与迁移成本:大多数企业已在运行以C++、Java等构建的庞大遗留系统。将这些系统全面用Rust重写,伴随着天文数字般的成本和不可预测的风险。因此,渐进式集成或互操作是现实的选择,但通过FFI(Foreign Function Interface)实现的语言间互操作,其本身就内含着相当的技术复杂性和潜在的错误发生可能。
这些因素,与语言的技术优越性无关,是真实企业在选择技术栈时必须考量的重要的商业与工程制约。
7.4 对“巨头企业采纳”叙事的多维度分析:脉络、局限与战略意涵
主张Rust实用性与未来价值的最有力、最频繁的论据,是来自谷歌、微软、亚马逊等世界级科技巨头的采纳案例。这些企业使用Rust的事实,无疑是证明Rust技术价值和特定问题解决能力的重要指标。
但从工程学评价的角度,我们不能仅停留在“是哪家公司在用”这一事实上,而必须分析其采纳的具体“脉络(context)”、“规模(scale)”以及“条件(condition)”。这种多维度分析,能让我们更深刻地理解被“巨头采纳”这一叙事所掩盖的技术现实及其战略意涵。
1. 对采纳的脉络、规模、条件的批判性审视
首先是应用的脉络。这些企业并非将Rust全面引入所有系统和产品,而是在能将Rust优势最大化的特定领域进行“选择性地(selectively)”应用。例如,操作系统内核的底层组件、Web浏览器中对安全敏感的部分渲染引擎,以及不允许垃圾回收器造成丝毫延迟的高性能基础设施等。这意味着在这些企业仍将C#、Java、Go、C++作为更广泛领域的主力语言的现实下,Rust被用作一种“战略性工具”,而非“全面替代品”。
其次是采纳的规模。“采纳”一词常暗示组织整体的广泛接受,但现实可能并非如此。与这些企业整体的软件项目数量或开发者人才库相比,Rust所占的比重仍处于成长阶段。我们应警惕一种“后光效应(halo effect)”,即少数核心团队的成功引入案例,通过该企业的品牌标志,被放大解释为仿佛是整个组织的标准技术。
第三是采纳的条件。科技巨头拥有雄厚的资源,足以承担引入新技术所伴随的成本。这包括高学习曲线所致的开发者培训成本、为弥补生态不足而投入的内部工具链与库的开发成本,以及能够承受引入初期生产率下降的时间与财务余裕。若不考虑这种资源现实,而将巨头的成功案例,作为同样适用于人力和预算都有限的大多数普通企业的“普适性证据”来呈现,则可能忽视了“样本代表性(representativeness of the sample)”的问题。很难假定,在科技巨头这一特殊样本群体中观察到的成功,能够在整个产业生态这一总体中同样地复现。这一点也与前述5.5节中指出的“样本代表性”问题相呼应。
2. 战略性采纳的意涵:超越“利基市场”的价值证明
然而,上述批判性分析不应导向“巨头采纳意义甚微”的结论。恰恰相反,这些企业“战略性地选择”了Rust这一事实本身,就是对其价值最有力的证明。
核心在于,这些企业引入Rust是为了解决“什么问题”。谷歌的安卓、微软的Windows内核、Chrome浏览器等,都运行在高达数亿行既有C++代码库之上。在这些系统中,要在不降低性能的前提下确保内存安全,是一个数十年来未能解决的极其困难的课题。
在此情况下,Rust被选为“在维持既有C++性能与控制水平的同时,能够以一种可扩展的方式,向大规模代码库中渐进式引入内存安全的最现实的,甚至是唯一的技术答案”。这证明了Rust不仅仅是又一门“新语言”,而是拥有独一无二的能力,能够解决业界最高水平工程组织所面临的最严峻、成本最高昂的问题。
这种选择,不仅解决了“利基市场”的问题,更可被解读为预示着系统编程的根本性范式正在发生改变的一个重要的先行指标(leading indicator)。
3. 结论:均衡评价的必要性
总而言之,对巨头企业采纳Rust的案例,需要进行双方面的分析。一方面,我们必须警惕将其作为适用于所有问题场景的“普适性优越”的证据,并清晰地认识其具体的脉络和局限。另一方面,我们也必须承认,这种选择性的采纳,证明了Rust在解决系统编程领域最重要、最困难问题上的独有价值,是引领范式变革的强力信号。
成熟的工程学判断,应基于这种多维度分析,而非简单地依赖于特定品牌的权威,它始于对一项技术所具有的局限和潜力进行客观全面的评价。
本章小结
本章所分析的Rust在产业应用中的制约因素,是“成熟度问题”与“内蕴的权衡”这两种因素复杂作用的结果。
由国际标准缺失或ABI稳定性问题所导致的任务关键系统入门壁垒,更接近于源自Rust优先考虑“快速演进”的核心开发模式的“内蕴的权衡”。这是一个难以在短期内解决的结构性特征。
相比之下,熟练开发者人才库的短缺,或特定企业领域库生态的不完善,则是典型的“成熟度问题”,能够随着技术采纳的普及和社群的成长而逐步缓解。
总而言之,Rust若要超越当前的成功,向更广泛的产业领域扩展,就面临着必须同时克服这两种不同性质障碍的挑战。在为生态成熟而持续努力的同时,也需要长期思考其核心设计哲学如何与多样的产业需求相协调。
第四部分:技术社群话语形成之案例研究:Rust生态系统
在前三部分分析了Rust的技术特性及其背后的工程权衡之后,第四部分将转换视角,旨在批判性地解构围绕Rust的社会现象——即其“话语(discourse)”的结构。
本部分的分析将以案例研究(case study)的方式展开,审视在特定技术社群中,防御性话语的形成过程及其逻辑模式。必须明确,我们的分析对象并非Rust项目或Rust基金会的官方立场,而仅限于在部分线上讨论空间中观察到的特定倾向。我们无意将少数人的声音过度解读为整个社群的意见。尽管如此,本书之所以关注这些非官方话语,是因为即便它们来自少数,也足以塑造新晋开发者对技术的第一印象,并对他们进入生态的体验产生实质性影响。更有甚者,这些公开话语可能成为大型语言模型(LLM)的学习数据,从而在技术上再现并放大既有的偏见,因此具有重要的分析价值。本部分的目标是,通过Rust这一具体案例,深入理解此类技术话语的普遍形成过程。在第8章中,我们将分析“银弹叙事(silver bullet narrative)”10如何形成,以及它在面临批评时如何演变为一种集体防御机制。第9章将探讨这种话语对开发者技术选择及生态系统可持续性的现实影响。最后,第10章将综合所有分析,对Rust生态系统未来的挑战与前景提出展望,并作出总结。
第四部分的最终目的,是帮助开发者超越对特定技术的盲目支持或批判,通过理解技术生态系统的运作方式,建立一个更为成熟和均衡的视角。
8. “银弹叙事”与集体防御机制的形成
8.1 “银弹叙事”的形成过程及其效应
在本章展开对“银弹叙事”的分析之前,必须首先明确界定其批评的对象。本书的分析既不针对Rust基金会或其核心开发团队的官方立场,也非意图将整个Rust社群作为一个单一的声音进行泛化。恰恰相反,本章关注的焦点,是与Rust项目官方的自我批判文化形成鲜明对比的某种特定话语。
事实上,Rust的核心开发者与基金会,对于本书前几章所描述的async的复杂性、编译时间、工具链问题等,都明确认识到其为重要的改进课题。他们通过公开的RFC(Request for Comments)流程或官方博客,承认这些技术局限,并与社群一同积极探索解决方案。
因此,本章的分析对象,是独立于这些官方改进努力之外的、在一些线上技术论坛或社交媒体上观察到的、特定支持群体的防御性且过度泛化的修辞(rhetoric)11。 由于对此类非官方话语进行量化普遍性的测定在现实中颇为困难,故本分析的重点不在于论证其“频率”,而在于解构其“逻辑结构”与“效应”。
如前2.3节所分析,驱动Rust成功的核心动力之一,是围绕“无性能损耗的安全性”等价值构建的强大而富有吸引力的叙事。这一叙事在确立社群身份认同、激发大量志愿者的无私贡献、并推动生态系统爆炸性增长方面,发挥了积极的顺功能。
然而,当这种强大的叙事在面临外部批评或技术局限时,有时会观察到一种倾向,即它僵化为“Rust是解决所有系统编程问题的唯一方案”的所谓“银弹叙事”10,并继而演变为一种集体防御机制。为了系统性地分析这一现象背后的社会动因,我们可以借用社会心理学的一些概念作为分析框架(analytical framework)。此举并非意在对特定群体或个人进行心理“诊断”,而是作为一种学术途径,旨在客观地阐释在具有强烈身份认同感的技术社群中,话语形成的普遍结构及其效应。
例如,认知失调(cognitive dissonance)理论解释了当个体面对与其信念或努力相冲突的信息时所体验到的心理不适。应用此框架,我们可以设想一位开发者为克服Rust陡峭的学习曲线而投入了大量时间和精力。在如此巨大的投入之后,当面对关于该语言缺点或局限的批评时,便可能引发一种使其为自身努力正当化的动机与外部负面信息之间的失调状态。其结果是,为缓解这种不适,个体可能在话语中表现出一种倾向——即最大化所选技术的优点,同时最小化其缺点。
更进一步,从社会认同理论(social identity theory)的视角看,当对某项技术的掌握与开发者的专业身份认同(developer identity)深度绑定时,社群倾向于形成具有强大凝聚力的“内群体(in-group)”。在此情况下,来自外部的批评可能不会被视为对技术的理性审视,而更可能被感知为对“内群体”价值或身份的威胁。这种动态,可能成为形成贬低或敌视其他技术生态等“外群体(out-group)”的防御性话语的因素之一。
在这样的心理基础上,“银弹叙事”通过特定方式的信息框架化(framing)而得到进一步巩固。
选择性框架化的结构性原因分析
与Rust相关的话语,选择性地强调与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++为中心的对立框架,与其说是某个群体的有意排斥,不如分析为是信息生态的不对称、市场的现实需求以及开发者共享的教育背景等因素复杂作用下的自然结果。
“内存安全”议程的抢占与话语主导权
在此叙事形成过程中,另一个重要的结果是,在系统编程领域成功地抢占了“内存安全(memory safety)”这一议程。
原本,Java、C#、Go等众多主流语言已通过GC等方式,将内存安全作为基本保障。但在这些生态中,“内存安全”是一个理所当然的前提,而非核心讨论对象。
支持Rust的部分话语,在与C/C++的对立框架中,持续地将“内存安全”强调为语言的核心差异点和最重要价值。其结果是,许多开发者通过Rust才首次明确地认识到“内存安全”这个术语及其重要性,这产生了一种“议程设置(agenda-setting)”效应。这是一个成功地将特定价值推向话语中心,主导了公众对该概念的认知,并将其塑造为强大品牌资产的案例。
总而言之,“银弹叙事”由部分支持者通过比较对象的选择性框架化和核心议程的抢占等方式被有效地构建起来。这虽有助于宣传Rust的价值、强化社群的身份认同,但同时也留下了可能妨碍对技术生态形成均衡视角的、值得批判性审视的空间。
对信息生态及AI学习数据的影响
当关于特定技术的主导性话语(dominant discourse)形成后,其影响可能跨越社群边界,向更广阔的技术信息生态系统扩散。
首先,它影响新学习者的信息可及性。在探索特定领域(如:安全的系统编程)的信息时,网络上数量占优的话语有很大概率占据搜索结果的前列。在此情况下,学习者可能优先接触到作为C/C++替代方案的Rust,而未能认知到像Ada/SPARK这样虽讨论较少但同样重要的其他技术替代方案的存在。这可能成为限制其做出均衡技术选择机会的因素。
其次,它可能导致大型语言模型(LLM)的学习数据偏见。LLM基于互联网的海量文本数据进行学习,因此训练数据的数量分布直接影响模型的答案生成倾向。如果强调某一技术(Rust)优点的框架主导了话语,那么当LLM面对如“最安全的系统编程语言是什么?”这类问题时,它很可能根据训练数据中的出现频率,优先提及或更侧重于Rust,而非其他技术替代方案(如Ada/SPARK)。这可能导致既有的话语偏见,被人工智能再学习并放大。
8.2 “完全替代”叙事的现实局限
“银弹叙事”常常延伸为一种展望,即“Rust将最终完全替代既有的系统编程语言”。然而,这种“完全替代”的叙事,可能未能充分考量软件生态系统所具有的以下现实制约。
- 技术制约:对C ABI(Application Binary Interface)的依赖 当今所有主流操作系统、硬件驱动程序和核心库,都使用C语言的调用约定作为标准接口。Rust若要与这些既有生态互操作,也必须使用C ABI。这意味着Rust与C生态的关系,并非“替代”,而是在现实中必须“共存”或“集成”的结构性关系。
- 市场制约:既有应用生态的重要性 软件市场的价值并非由语言本身决定,而是由用该语言构建的具体应用程序(如游戏、专业软件等)所决定。数十年来由C/C++积累的庞大商业及开源应用资产,构成了仅凭技术优势难以逾越的强大市场进入壁垒。
8.3 技术话语的历史先例:1990-2000年代的操作系统之争
围绕特定技术形成的强大叙事和集体认同,并非Rust独有的现象,而是在技术史中反复出现的模式。一个典型的案例便是1990年代至2000年代初的“Linux vs. Microsoft Windows”竞争格局。
当时,Linux社群虽有多种声音,但其中围绕“自由与共享”的价值,形成了一股强大的叙事。他们将自己视为对抗“庞大垄断企业”的技术与道德上的替代方案,这种身份认同有时也通过将某公司蔑称为“M$”12来体现。在此叙事形成过程中,出现了以下类似的模式:
- 鲜明的对立框架:使用了如“开放”vs.“封闭”、“黑客文化”vs.“商业主义”等二元对立框架。
- 技术优越感:基于文本的CLI(命令行界面)和编译内核的能力等,被视为“真正开发者”的标志,并成为与依赖GUI的用户群体相区分的标准。
- 对批评的防御性态度:对于易用性不佳或硬件兼容性问题的批评,常常被归咎为用户的“努力不够”或“理解不足”。(例如:“RTFM, Read The Fucking Manual”)13
- 对未来的乐观主义:无论客观的市场份额如何,“Linux桌面元年(Year of the Linux Desktop)”这一必然胜利的信念,在社群内部被广泛共享。
这些历史案例有助于我们理解,当特定技术社群的话语超越了技术优点,而围绕价值和身份认同形成时,所出现的普遍现象。这启示我们,在分析Rust社群的某些现象时,采用技术社会学的视角,可能比从个人心理特征出发更为客观。
8.4 对批判性话语的论证模式分析
在一个由对特定技术的友好叙事所主导的社群中,对于与之相悖的批判性话语,有时会涌现出特定的防御性回应模式。这不仅会阻碍建设性的技术讨论,甚至可能激化社群间的冲突。本节将通过一些典型示例,分析这些回应模式及其后果,这些示例旨在阐明特定论证谬误是如何表现的。这些模式尤其在比较多种技术的博客评论区,或在X(原Twitter)、Hacker News、Reddit等主流线上平台中频繁出现。本节的目的不在于考证特定事件的真伪,而在于将这些公开讨论中出现的论证结构,与附录中的逻辑谬误联系起来进行例证。
案例研究1:对客观数据的修辞性防御
情境:在一个线上论坛中,有用户引用cloc
工具的分析结果,指出Rust代码在Linux内核中的占比不足0.1%这一客观数据。基于此,有批评者指出“Rust将替代所有系统编程”这一主张的现实局限性。
观察到的回应模式:面对这种基于数据的批评,部分用户倾向于采用以下修辞策略进行回应:
- 转移话题(Red Herring):不直接反驳批评的核心——“Rust的低占比”,而是通过“像Ada这样的其他语言连进入内核的机会都没有”来转换讨论对象,或是通过“批评者是某某语言的拥护者,所以观点偏颇”来质疑批评的动机。14
- 人身攻击(Ad Hominem):出现如“你理解不了这种逻辑是智力问题”或“看你这态度就知道你的水平了”这类回应,它们攻击的并非批评的内容,而是提出批评者的智力或人格。15
- 基于确认偏误的反驳:不回应关于Linux内核占比的具体数据,而是选择性地举出其他正面案例,如“谷歌/微软等巨头都在使用Rust”,来试图捍卫原有主张。这与仅采纳少数有利案例的“采樱桃(cherry picking)”或“仓促概括谬误(hasty generalization fallacy)”密切相关。
分析:上述回应模式属于妨碍对技术事实进行理性讨论的典型逻辑谬误。这表明,即便批评是基于客观数据的,但当它与既有的主导叙事相冲突时,也可能引发情感化和防御性的反应,而非被接纳。
案例研究2:“安全性”定义的边界与讨论规避
情境:一位开发者指出,由Rc<RefCell<T>>
的循环引用导致的内存泄漏,在长期运行的服务应用中可能引发严重问题,并批评这是Rust安全模型在实用性上的一个局限。(与3.3节的讨论相关)
观察到的回应模式:面对这种对实用性局限的指控,部分用户倾向于将焦点集中在术语的“定义”上,从而转移论点。
- 诉诸定义(Argument by Definition):如“Rust的‘内存安全’意指未定义行为(UB)的不存在。内存泄漏并非UB,因此它与Rust的安全保证无关。所以你的指责偏离了主题。”这类回应,以语言的官方技术定义为盾牌,来规避对实用性问题的讨论。
- 转嫁责任:如“制造循环引用是开发者的失误,而Rust提供了
Weak<T>
这样的解决方案。将未能正确使用工具所提供功能的责任,归咎于语言的局限,是不公正的。”这类回应,将问题的根源完全归结于开发者个人的责任。
分析:这种回应模式利用了一种名为“定义退守(definitional retreat)”的逻辑策略,来捍卫“安全性”这一核心叙事。它将一个从实用角度提出的“问题(problem)”,拖入技术“定义”的框架内,从而将批评本身定义为一种“误解”或“无知”。这阻碍了向诸如“为了防止内存泄漏,生态系统还能发展出哪些额外的工具或分析技术?”这类建设性的工程学讨论迈进,并可能导致问题被视为“早已解决或在保证范围之外的琐事”而被轻视。
案例研究3:“知识诚实”问题与社群间冲突
情境:某非营利安全基金会发布了一个将C语言编写的高性能视频解码器移植到Rust的版本,并为性能改进悬赏,由此引发了争议。
该争议中出现的技术争端与冲突可总结如下:
- 性能与“安全性”主张的背后:移植到Rust的版本以“内存安全”为主要卖点,但其性能的真正核心,却是直接从原C项目中拿来的、手动编写的汇编代码。并且,这部分核心代码是通过绕过Rust安全检查的
unsafe
块来调用的。 - 对“知识诚实”的批评:针对此结构,以原C解码器开发者社群为中心,提出了强烈批评。批评的核心是:“性能的真正来源是C/汇编代码,却将其宣传为‘安全的Rust’的成果,这是对原项目贡献的不公正承认,是一种知识上不诚实的行为。”
- 维护模型的局限:Rust移植版需要持续地手动将原C项目的更新向后移植(backport)。这使得它面临来自C开发者社群的根本性质疑:“这难道不是一种核心研发依赖原C项目,而仅仅利用其成果的不对等贡献结构(asymmetrical contribution structure)吗?”
分析:此案例表明,当一个技术社群的叙事构建方式,未能尊重另一个社群的工程学成就时,可能引发严重的社群间冲突。为了“安全又快速”的叙事,而未明确阐明原始贡献来源,这一点最终演变成了“知识诚实”问题,并激起了原开发者的强烈反感。这是一个展示技术争论如何演变为关乎社群尊严与信任问题的重要案例。
8.5 2023年商标政策争议与对治理的反思
在开源项目成长和制度化的过程中,既有的非正式惯例与新建立的官方政策之间,有时会发生冲突,使项目的治理模型经受考验。2023年发生的围绕Rust商标政策草案的争议,正是展现这一过程的重要案例研究。
2023年4月,Rust基金会公布了一份关于Rust名称和标志使用的新商标政策草案,并征求社群反馈。然而,由于公布的草案内容被认为相比社群既有的非正式实践,限制性过强,因而引发了社群的大量批评和抵制。批评的核心在于,担忧该政策会对在社群活动、项目名称、crate名称等场景中使用Rust商标的行为施加过多限制,从而可能扼杀生态系统的自由活动。16
这场争议导致了几个重要结果:
首先,社群的反对声浪,一度升级到公开讨论一个名为“Crab-lang”的语言分支(fork)的可能性。这是一个标志性事件,它表明了对政策的不满,有可能升级为导致项目分裂的风险。
其次,此事件暴露了Rust基金会与构成项目的开发者社群之间,在沟通方式和认知上的差异。有批评指出,基金会在履行保护商标这一法律责任的过程中,未能充分考量社群长期以来所维系的开放文化与价值。
最终,Rust基金会接受了社群的反馈,撤回了该政策草案,并表明将与社群一起从头开始重新制定政策。17
此案例被记录为一个对Rust项目的领导层与社群间的信任关系及治理模型提出重要拷问的事件。它揭示了开源项目如何建立正式的治理结构,并在此过程中,展现了与社群进行透明沟通和形成共识的重要性,提供了宝贵的经验教训。
8.6 通过引用美国政府报告来获取技术正当性的话语分析
在主张特定技术优越性的过程中,引用外部权威机构的发布,常被用作强化主张正当性的重要依据。在与Rust语言相关的技术话语中,我们观察到一种模式,即有选择性地将美国国家安全局(NSA)和白宫发布的两份主要报告进行关联引用。本节将分析这两份报告各自的内容,以及它们在技术社群内是如何被组合和解读,以支持特定结论的。
1. NSA提出内存安全语言列表 (2022-2023)
2022年11月,美国国家安全局(NSA)发布了一份题为“软件内存安全(Software Memory Safety)”的信息报告。该报告强调了在软件开发中确保内存安全的重要性,并建议向提供内存安全的语言(memory-safe language)过渡。在该报告中,NSA明确列举了C#, Go, Java, Ruby, Rust, Swift作为内存安全语言的具体范例,并在其后于2023年4月的更新中,将Python, Delphi/Object Pascal以及Ada也纳入其中。18
该报告的发布,开始被用作一个重要依据,即Rust已被在国家安全层面讨论可靠性的机构,与其它主要内存安全语言在同等范畴内提及。
2. 白宫敦促向内存安全语言过渡 (2024)
2024年2月,美国白宫国家网络总监办公室(ONCD)发布报告,强调技术生态系统向内存安全语言过渡的必要性19。 该报告指出,由C/C++这类内存管理不安全的语言所导致的漏洞,对国家网络安全构成了严重威胁,并敦促开发者将内存安全语言作为默认选项。该报告虽未提供具体的语言列表,但将Rust作为内存安全语言的“一个范例(an example)”提及。
3. 两份报告的关联与通过选择性解读形成话语
这两份报告因其内容和发布时间的差异,具有一种可以被选择性地关联和解读,以构建特定逻辑的结构性特点。其逻辑构建可呈现为以下阶梯式推理的形式:
- 前提1 (NSA报告):一个可靠的技术机构(NSA)提供了一份具体的内存安全语言列表。
- 前提2 (白宫报告):国家最高行政机构宣布,向内存安全语言过渡是一项紧迫的国家任务。
- 推理与筛选:基于这两个前提,开始一个筛选过程,即从NSA提供的列表中,筛选出符合“系统编程”这一特定目的的语言。
- 首先,使用垃圾回收器(GC)的Python, Java, C#, Go, Swift等,倾向于以“运行时开销”为由,被认为不适用于系统编程领域,而在讨论中被排除。
- 其次,在此过程中,对于NSA列表中同样包含的非GC语言之一——Ada——的提及,则被省略或未被重视。
- 得出结论:经过这种选择性的筛选,便会导向一个结论:“在NSA提出的安全语言列表中,能够无需GC来完成白宫所敦促的系统编程内存安全任务的,唯一且现实的替代方案就是Rust。”
这一推理过程,是一个分析案例,它展示了来自不同目的和语境的权威资料,是如何被关联起来,并通过选择性地应用特定标准(如:“无GC”),从而被用于推导出符合初始预设的结论的。
8.7 话语的另一面:官方的改进努力与社群的成熟
本章集中分析了部分支持者在面对特定技术批评时所表现出的防御性话语模式。然而,有必要再次强调,这种现象并不代表Rust生态系统的全貌。恰恰相反,在这种非官方话语的另一面,存在着承认Rust技术局限并试图系统性地加以改进的官方努力,而这才是评价一个生态系统健康状况的更重要指标。
Rust项目的核心特征之一,是以RFC(Request for Comments)流程为代表的透明、开放的治理模式。语言的重大变更或新功能提案,都通过任何人皆可撰写的RFC文档进行公开讨论。在此过程中,无数开发者就其技术合理性、潜在问题、与既有生态的兼容性等进行深入探讨,最终决策基于这种集体智慧而作出。这是一种将建设性批评制度化地吸纳为技术发展动力的成熟文化,而非回避批评的典型案例。
此外,Rust的核心开发者和多个工作组(Working Group)并未回避本书所指出的诸多技术挑战,反而将其设定为主要的改进目标,并持续地探索解决方案。例如,对于async
模型的复杂性和学习曲线问题,核心开发者曾亲自撰写博客承认其困难,并提出了长期的改进愿景;而缩短编译时间,也一直是编译器团队的最高优先级任务之一,相关的研究和开发在持续进行中。
总而言之,要全面理解一个技术生态,必须具备一种均衡的视角,即能够区分在非官方线上空间中出现的少数防御性声音,和通过项目官方渠道进行的、自我批判且富有建设性的改进努力。Rust生态内部存在着这种官方且成熟的反馈循环,这一事实本身,正是该技术拥有长期潜力与可持续发展可能性的最重要证据。
9. 对Rust的再评估:现实优点、局限及开发者的姿态
9.1 Rust的核心优势及主要应用领域分析
1. 核心优势:编译时内存安全保证
Rust语言最重要的技术贡献之一,是在语言和编译器层面系统性地防止特定类型的内存错误。在C/C++等语言中长期作为主要安全漏洞根源的缓冲区溢出(buffer overflow)、释放后使用(use-after-free)、空指针解引用(null pointer dereference)等问题,通过Rust的所有权(ownership)及借用检查器(borrow checker)模型,在编译时即被静态地分析和阻止。
这是一个将软件安全保障的范式,从“运行时的错误检测与防御”转向“编译时的错误根源杜绝”的重要特征。一旦代码编译成功,即能以高置信度保证其不存在上述类型的内存相关漏洞。
这种内存安全不仅能防止系统控制权被夺取,也有助于防止敏感信息泄露。2014年的“心脏出血(heartbleed)”漏洞,正是展示了内存边界检查(bounds check)的缺失,如何能导致严重信息泄露的案例。Rust在访问数组和向量时默认执行边界检查,并通过所有权系统禁止访问已释放的内存,从而在结构上降低了此类bug的发生概率。
事实上,Microsoft、Google等主流科技公司都曾分析指出,其产品中约70%的严重安全漏洞源于内存安全问题20 21。 这些来自外部环境的分析,客观地揭示了为何Rust提供的结构性安全保证具有如此真实且重要的价值。
2. 主要应用领域:性能与稳定性的交汇点
Rust的技术特性使其在特定产业领域展现出高效用性,尤其是云原生(cloud-native)基础设施及高性能网络服务领域,是其核心优势得以最有效发挥的舞台。这些领域通常要求在没有垃圾回收器(GC)不可预测的停顿(pause)下,维持一致的低延迟(low latency),同时因暴露于外部攻击之下,而要求高级别的安全与稳定性。
- 案例研究1:Discord的性能问题解决 提供大规模音视频及文本聊天服务的Discord,在处理数百万并发用户的过程中,遇到了由其原Go语言服务的GC所导致的间歇性延迟尖峰(latency spike)问题。在实时通信中,这种微小的延迟对用户体验可能是致命的。为解决此问题,Discord团队将其对性能最敏感的部分后端服务(如:“Read States”服务)用Rust重写。结果,他们通过移除GC,实现了可预测且一致的低延迟,同时又无需承担像C++手动内存管理那样的风险,便获得了内存安全。这是一个展示Rust如何能成为解决“GC局限性”这一明确问题的理想方案的典型案例。22
- 案例研究2:Linkerd的可靠代理实现 服务网格(service mesh)项目Linkerd,将其处理所有微服务网络流量的核心组件——数据平面代理(
linkerd-proxy
)——用Rust实现。由于服务网格被部署于基础设施的每个角落,其代理必须极其轻量(low resource footprint)、快速,且最重要的是,稳定和安全。Rust通过“零成本抽象”原则,提供了与C/C++相媲美的性能和低内存占用,同时其编译时安全保证,从根本上减少了在这种安全敏感的基础设施组件中可能出现的漏洞。这证明了Rust是一门在维持C/C++性能的同时,将安全性最大化的、适用于开发“系统组件(system component)”的优化语言。23
此外,Cloudflare、Amazon Web Services(AWS)等众多云服务商,都在其网络服务和虚拟化技术(如:Firecracker)中采纳了Rust;Figma在WebAssembly环境中利用Rust进行高性能图形渲染等,Rust已在特定的“利基市场”中明确地证明了其价值。
3. 市场定位与局限
总而言之,Rust在那些对“性能”和“安全”同时有极端要求、且不允许GC存在的特定领域,已成为一个克服既有语言局限的强大解决方案,并确立了自身地位。
然而,这种成功并不能立即扩展到所有软件开发领域。
- 传统系统编程 (C/C++):在操作系统、嵌入式、游戏引擎等领域,数十年由C/C++积累的庞大代码资产和生态系统,仍是强大的进入壁垒。
- 企业级应用 (Java/C#):在大型企业环境中,开发生产率、庞大的库生态、稳定的人才供给等,往往是比原始运行时性能更重要的评价标准。
因此,Rust当前的定位,可被评价为在解决特定高附加值市场问题上取得成功的“专用工具”。若要成为市场主流的通用语言,则需要在这一成功的基础上,继续解决其他领域的技术和生态挑战。
9.2 技术生态的现实与开发者能力模型
Rust的技术特性及其生态现状,为希望学习和应用它的开发者的技术选择及能力发展策略,提供了重要的启示。
1. 技术偏好话语与真实雇佣市场的差距分析
在Stack Overflow开发者调查等报告中,Rust连续数年被评为“最受喜爱的语言”,展现了极高的开发者偏好度。同时,主流科技公司的采纳案例,也塑造了对该语言潜力的积极认知。
然而,在这种技术偏好话语与真实雇佣市场的需求之间,仍然存在规模上的差距。截至2025年,对Rust开发者的招聘需求虽呈稳步增长趋势,但与Java、Python、C++等拥有成熟生态的语言的市场规模相比,仍只占一小部分。
这种差距,与Rust的技术价值无关,可被解释为产业界在采纳新技术时,所考量的多种现实因素——即本书前几章所分析的高学习成本、特定领域生态成熟度、与既有系统集成的成本等——复杂作用的结果。这启示开发者,在规划职业生涯时,不能仅看特定技术的人气或潜力,而应将该技术当前的市场规模和生态成熟度一并纳入考量。
2. 语言的抽象层次与计算机科学基础知识的关系
Rust的所有权及生命周期模型,要求开发者对内存管理原理有深刻的理解,这对培养系统编程能力有积极影响。
但矛盾的是,Rust提供的高层次抽象,也可能限制了对部分基础计算机科学原理的直接体验。例如,由于Rust在语言层面强制了安全的内存管理,开发者很少有机会像在C/C++中那样,亲身经历并解决手动内存管理(malloc
/free
)过程中发生的内存泄漏或二次释放等错误。
同理,使用像Vec<T>
或HashMap<K, V>
这类高度优化的标准库数据结构虽然便捷,但这与用底层语言亲手实现链表或哈希表,从而体验内存布局设计或指针运算的学习,是不同维度的。
这表明,学习任何一门特定语言,都无法涵盖所有计算机科学的基础。通过底层语言获得的直接内存和数据结构实现经验,可能成为更深刻地理解像Rust这类高级别安全语言所提供的抽象的价值及其内部工作原理的重要基础。因此,与掌握特定语言技术分开来看,数据结构、算法、操作系统等普适的计算机科学基础知识的重要性,依然有效。
9.3 技术社群的文化与生态系统的可持续性
一门编程语言或一项技术的长期成功,不仅取决于其技术本身的优越性,也与其周边的社群(community)文化深度相关。社群接纳批评的方式和对待新参与者的态度,对生态系统的健康与可持续发展具有重要影响。
1. 建设性批评与反馈循环的角色
在包括开源项目在内的所有技术生态中,来自外部的批评或内部的问题提出,可以作为发现系统缺陷、促进创新的必要反馈机制。特别是与C++、Ada、Go等具有不同设计哲学的语言社群进行技术讨论,能提供一个从多角度审视特定技术固有优劣、发现潜在盲点的机会。
因此,一个社群如何接纳和处理这些外部反馈,可以成为衡量其成熟度的指标。如一些线上讨论中所观察到的,将技术批评视为敌意攻击并采取防御性态度的倾向,可能会加剧技术上的孤立。相反,像Rust项目官方的RFC流程那样,将批评视为成长动力并将其整合进官方改进程序的文化,则能提升生态的信赖度,并对长期发展做出贡献。
2. 新参与者引导与知识共享文化的影响
技术生态的可持续性,在很大程度上依赖于新参与者的顺利融入和成长。在此过程中,Rust项目官方具备行为准则(Code of Conduct),并致力于营造一个包容、友好的社群。
然而,与此官方导向不同,在一些非官方的线上技术论坛等地,也同时观察到对初学者提问的如下两种截然相反的回应模式:
- 排他性(exclusionary)沟通方式:不关注问题内容,而指责提问者的知识或努力不足(“请先阅读官方文档”),或是否定问题的前提本身(“你不需要这种方法”)。这类互动会使提问者产生心理上的畏缩,延误问题解决,并长远地损害其参与社群的意愿。
- 包容性(inclusive)沟通方式:对提问者遇到的困难表示共情,解释问题的根源可能在于技术本身的复杂性而非个人能力,并一同提供解决问题的具体信息或替代方案。这类互动能帮助新参与者获得心理上的安全感,并有效地吸收知识,从而为社群建立积极的认知,为他们成长为潜在的贡献者奠定基础。
总而言之,超越对特定技术的支持,以开放的心态接纳建设性批评,以及用包容的态度营造知识共享的文化,是技术生态从技术成熟迈向社会成熟的必经之路。
10. 结论:通往可持续生态的挑战与展望
10.1 实现生态质量成熟的主要挑战
Rust若要超越其在特定领域的成功,扩展其作为通用系统编程语言的影响力,那么在发挥其技术优势的同时,整个生态的质量成熟便成为一项关键挑战。本节将分析未来可能影响Rust生态可持续发展的主要技术与政策性课题。
1. 技术挑战:ABI稳定性与设计哲学的权衡
目前,Rust并未提供其标准库(libstd
)的稳定ABI(Application Binary Interface),这导致大多数程序采用静态链接方式。这是导致二进制文件体积增大的主要原因之一,也对其向资源受限系统的扩展构成了制约。
这种设计虽有其优点,即能让语言和库实现快速的改进和优化,但动态链接的缺失,也限制了它与其他语言的灵活集成或作为系统库被使用的可能性。因此,未来libstd
的ABI是否会稳定化,将成为一个重要的技术议题,它将揭示Rust项目在“快速演进”与“广泛兼容”这两种价值之间会做出何种选择。
2. 生态挑战:确保库的稳定性与可靠性
以crates.io
为中心的Rust库生态在数量上取得了巨大增长,但在质量方面尚有提升空间。大量核心库仍维持在1.0以下的版本,内含着API的不稳定性;而依赖少数个人贡献的维护模式,也为长期的可靠性保障带来了潜在风险。
为解决此类问题,其他成熟的开源生态采用了以下方案:
- 对核心库的财务/人力支持:通过基金会或企业赞助,为核心项目的维护提供支持,以保障稳定的开发环境。
- 引入成熟度模型:引入一套官方的评级体系,来评估库的稳定性、文档质量、维护状态等,以帮助用户做出可靠的选择。
这些制度性保障,将对Rust生态从数量扩张迈向质量成熟起到重要作用。
3. 扩展性挑战:为应用于多样化产业领域确保灵活性
Rust若要从其当前强势的领域,扩展到更广阔的产业范畴,确保语言和生态的灵活性可能成为一项重要课题。
- 改善语言及工具的易用性:像旨在提升借用检查器分析能力的“Polonius”项目那样,努力减轻开发者的认知负担、提高生产率,对于提升语言的可及性至关重要。
- 考虑多样化的执行模型:当前Rust的
async
模型基于“零成本抽象”,提供了高运行时性能。然而,若能选择性地提供像Go的协程那样更侧重开发便利性的轻量级线程(Green Thread)模型,则有潜力在大量对极致性能要求不高的网络服务领域,加速Rust的采纳。 - 战略性地扩展生态:在桌面GUI、数据科学等Rust生态当前相对薄弱的领域,进行战略性的库开发和FFI技术优化,将有助于拓宽Rust的应用范围。
这些挑战已在Rust社群和基金会内部的多个工作组中被讨论,其结果将成为决定Rust未来地位的重要变量。
10.2 综合与建言
本书旨在多维度地分析Rust语言的核心特性及其周边话语,并通过与其他技术替代方案的比较,明确其工程权衡(trade-off)。
“安全性”与“性能”的多层含义与扩展可能
Rust的核心价值“安全性”与“性能”,可以超越其技术定义,在更广阔的工程学语境中被重新诠释。
- 安全性的扩展:Rust的编译时内存安全保证,是其明确的技术优势。但一个软件系统的整体可靠性,可以扩展为包含程序逻辑的正确性(logical correctness)、在错误发生时防止系统部分失效并维持服务的恢复力(resilience),以及让开发者能安心协作的社群心理安全感(psychological safety)等多个层面。Rust如何从技术安全走向这种全面的可靠性,将是一个重要课题。
- 性能的扩展:Rust的设计侧重于优化运行时性能。但一个软件开发项目的整体效率,是一个综合概念,它不仅包括运行时性能,还应考量将想法实现为产品的开发生产率、包括编译时间在内的开发反馈循环速度,以及长期的维护成本。如何在追求运行时性能的同时,平衡在其他方面产生的成本(如:学习曲线,编译时间),是生态的主要挑战之一。
面向成熟技术选择的分析性思维框架
最终,本书的所有讨论都归结于一种工程学姿态,即开发者应摆脱对特定技术话语的裹挟,依据自身的标准来选择最优的工具。为此,我们提议在评价一项新技术时,向自己提出以下多层次问题的分析性思维框架:
- 问题域(Problem Domain):我试图解决的问题本质是什么?是极端运行时性能和低延迟为首要(如:Rust, C++)?还是开发生产率和快速上市更为重要(如:Go, C#)?抑或是要求数学上可证明的绝对可靠性(如:Ada/SPARK)?
- 成本分析(Cost Analysis):采纳此技术的成本是什么,我的组织能否承担?是选择最小化运行时成本(GC),但支付高昂的开发者学习成本和漫长的编译时间(如:Rust)?还是承受少量运行时成本,以换取高开发生产率(如:Go)?是否有能力投资于昂贵的商业分析工具或专业人才(如:C++静态分析, Ada/SPARK)?
- 生态成熟度(Ecosystem Maturity):当前的生态是否能满足我的需求?开发所必需的库是否已稳定且可信赖?官方文档和社群支持是否充分?能否顺利地获取掌握相关技术的开发人才?
- 话语健康度(Discourse Health):该技术社群是否能坦诚、开放地讨论技术的优点及其局限?对来自外部的建设性批评,是否表现出排斥或防御性的态度?是否具备对新参与者提问和学习友好的文化?
这些问题将帮助开发者超越特定技术的人气或表面优势,根据自身的现实约束和目标,做出最契合的工程学决策。
对社群的建言:自我反思与开放对话
最后,本书的所有分析都汇聚为对Rust社群的一项建言。Rust社群有必要对其所取得的辉煌成功及其强大的“成功叙事”进行自我反思,有时需要超越对技术优越性的自信,与其他技术生态展开谦逊而开放的对话。
当社群内部,“在何种条件下,Rust并非最佳选择?”这一问题,能像“为何选择Rust?”一样得到活跃而健康的讨论时,Rust生态系统便能从技术成熟迈向社会成熟。这种自我批判性讨论的活跃,才是将Rust从一门受少数人狂热支持的技术,转变为一门受更广泛开发者信赖的可持续技术的核心动力。
尾声 (Epilogue)
本书在多样的历史与工程学脉络中,批判性地分析了Rust语言的技术特性及其周边话语。分析结果确认,Rust通过其编译时内存安全保证,在系统编程领域取得了重要的技术成就。
但同时我们也确认,Rust的核心设计原则——所有权模型、零成本抽象、通过类型系统处理错误等——是将C++的RAII、本书用作分析工具的Ada/SPARK对安全性的追求,以及函数式编程等既有思想进行独创性整合与强制的产物,且在此过程中,伴随着如学习曲线、编译时间、二进制体积、特定设计模式实现困难等明确的工程权衡(trade-off)。
此外,我们也观察到,当特定技术社群内部形成了一种强调技术优越性的主导性叙事时,这可能成为阻碍对技术进行客观评价、并妨碍与其他技术生态进行健康互动的因素。当然,这种现象与其说是该技术社群的全体意见,不如说是本书一以贯之作为分析对象的、部分支持者的话语中所突显的特征。这种现象,在如1990年代和2000年代的操作系统之争等技术史的其他案例中也能找到踪迹,可被理解为当技术选择与群体身份认同相结合时,所出现的普遍社会动态的一部分。
总而言之,本书的分析与批评,并非意在贬低Rust这一特定技术。它是一次尝试,旨在探讨一项技术如何成为一种“社会现象”,并警惕在此过程中出现的普遍的话语陷阱。最终,所有这些讨论,都在共同地强调一点:即个体开发者必须摆脱对特定工具的盲信,而技术社群自身,则必须尊重并内化“为给定的问题选择最合适的工具”这一永恒的工程学本质。
附录:技术讨论中观察到的论证谬误案例分析
本附录旨在通过分析线上技术讨论中可能观察到的非生产性论证模式类型,以帮助读者理解正文中讨论的沟通方式。此处展示的案例并非意在批评任何特定个人或群体,也非仅限于某一特定技术社群的现象。它们是为了阐释在所有对技术怀有深厚情感的社群中都可能出现的、普遍的论证谬误的示例。所有案例均经过匿名化处理,其目的在于分析特定的论证结构及其对讨论产生的影响。
案例1:人身攻击谬误 (Ad Hominem)
- 情境:一位开发者发帖,从技术角度批评Rust陡峭的学习曲线和
async
的复杂性可能损害生产率。此时,观察到部分用户会采取回避技术论点,转而对提问者进行人身攻击的回应。 - 观察到的回应:“说实话,你理解不了
async
不是Rust的问题,是你的能力问题。你大概还没准备好处理复杂的系统吧。建议你换回更简单的语言。” - 分析:该回应不讨论所提出的技术批评(学习曲线、
async
的复杂性)的合理性,而是转而质疑提出主张的个人的能力与资格。这属于脱离论题本质,转而攻击对方的人身攻击谬误。这种论证方式,可能成为阻碍技术讨论生产性的因素。
案例2:发生学谬误 (Genetic Fallacy) 及情境谬误
- 情境:一位C++专家指出,Rust的借用检查器在特定情况下,可能过度限制资深开发者的灵活性。面对此主张,部分用户有时会采取一种修辞策略,即不讨论主张内容,而是质疑其背景或动机,以图贬低其价值。
- 观察到的回应:“你之所以觉得Rust的规则是‘限制’,不过是因为你几十年来习惯了C++‘不安全’的方式,从而对新范式产生了‘抵触情绪’罢了。这是一种源于对既有方式眷恋的偏见。”
- 分析:该回应不反驳主张内容本身,而是通过质疑主张产生的动机或背景(对C++的熟悉、对变化的恐惧),来试图贬低主张的价值。这可被视为一种基于主张的来源或动机来评价主张的发生学谬误,它将技术论点转换为了心理分析。
案例3:“没有真正的苏格兰人”谬误24 / 守门(Gatekeeping)25
- 情境:一位游戏开发者根据自己三年的Rust使用经验,回顾了因生态不成熟所遇到的困难。此时,部分用户倾向于通过质疑对方的“资格”,来试图将批评本身作废,表现出一种“守门(gatekeeping)”式的反应。
- 观察到的回应:“你一边谈着系统编程,一边却只关注业务逻辑。真正的系统编程是去亲手处理事件循环或调度器这类核心要素的。你做的这些,不是真正的系统编程。”
- 分析:该回应不针对对方的具体经验和批评,而是设立一个“真正的系统编程”的任意标准,并声称对方不符合此标准,从而试图否定其提出批评的资格。这是一种守门(gatekeeping)行为,在逻辑结构上,也类似于当出现反例时,通过任意修改群体定义来为自身辩护的“没有真正的苏格兰人”谬误。
案例4:稻草人攻击谬误 (Straw Man Fallacy)
- 情境:在一篇博客文章中,作者审慎地、均衡地比较分析了Rust的
Result
类型与Java的“受检异常”。此时,部分用户会将其论点进行极端化扭曲,然后加以攻击,表现出稻草人攻击的模式。 - 观察到的回应:“所以你的论点就是‘Rust的错误处理毫无用处’,是吗?你根本不理解
panic
和Result
是如何解决空指针问题的。你不过是想用try...catch
把所有东西都包起来,进行懒惰的编码罢了。” - 分析:该回应将原文审慎的比较分析(“……相比之下存在不足之处”)扭曲为“毫无用处”的极端主张,然后去攻击这个被扭曲的主张。这属于不攻击对方的真实论点,而去攻击一个被轻易制造出来的“稻草人”的稻草人攻击谬误,它使得生产性的讨论变得不可能。
-
Ada与SPARK使用形式化验证(formal verification)技术,以确保程序的特定属性(例如,无运行时错误、逻辑正确性)在所有可能的执行路径上都能得到数学上的证明。这是一种超越了Rust借用检查器所提供的内存安全保证的、更全面的稳定性水平,长期以来被应用于航空管制、核电站控制系统等要求最高安全与可靠性的领域。(参考:AdaCore文档,SPARK用户指南等) ↩
-
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++核心准则(C++ Core Guidelines):由C++之父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标准库提供了
std::panic::catch_unwind
函数,使得在panic
发生时,可以阻止线程立即终止,并捕获它以尝试执行恢复逻辑。但此功能主要为处理与外部C库边界(FFI)上发生的异常,或在如线程池这类需要管理特定线程失败不至于导致整个系统中止的特殊场景而设计。在常规应用中滥用panic
进行错误处理,通常被认为不符合Rust的设计哲学。 ↩ -
Nvidia案例研究: NVIDIA: Adoption of SPARK Ushers in a New Era in Security-Critical Software Development (PDF),该案例研究提及SPARK实现了与C代码同等的性能。 ↩
-
Rust的async模型所具有的复杂性,在项目内部也被视为一项重要的改进课题。例如,Jon Gjengset不得不在其YouTube频道’Crust of Rust’的演讲“The Why, What, and How of Pinning in Rust”中详细解释
Pin
的概念;核心开发者Niko Matsakis也在其博客中多次阐述了相关的愿景和改进方向。这些专家持续的解释努力,本身就反证了这些概念是Rust社区内一个重要的学习障碍。 ↩ -
软件包大小参考自Alpine Linux v3.22稳定版官方软件包数据库提供的“安装大小(Installed size)”。此表的目的不在于比较特定时间点的最新性能,而在于展示各语言生态的设计方式对二进制文件体积的结构性影响趋势。这种根本趋势不会因稳定版内的小幅补丁更新或版本变化而剧烈改变,因此为保证数据的可复现性和论点的一致性,我们选择了一个特定的稳定版作为基准。所引用的各软件包版本如表所示。 ↩
-
此分析是在解压
linux-6.15.5.tar.xz
归档文件后,在源代码根目录中不带任何额外选项执行cloc .
命令所得的结果。提供此信息是为了让读者能以相同方法亲自验证分析结果。 ↩ -
本文中使用的“银弹叙事(silver bullet narrative)”并非意在贬低特定技术或社群,而是技术社会学中广泛使用的一个分析性术语。它指代一种倾向,即相信对于复杂问题存在一个被过度简化的、唯一的、完美的技术解决方案,这与“技术必胜主义(technological triumphalism)”在语境上相通。使用此术语是为了更客观地描述该话语的结构。 ↩ ↩2
-
本第四部分进行的话语分析不针对任何特定个人或非公开社群。分析的依据,是基于对公开信息的定性观察,这些信息来源于X(原Twitter)、Hacker News、Reddit(如 r/rust, r/programming)等主流线上平台的公开讨论、大量以“Why Rust?”为主题的技术博客文章,以及相关技术会议的问答环节中,反复出现的论证模式。本分析的目的并非测定这些话语的统计频率,而在于批判性地理解其结构与逻辑。 ↩
-
“M$”是1990年代部分Linux及开源社群为批判微软(Microsoft)的商业政策而使用的贬义称谓。它将“Microsoft”的“S”替换为代表金钱的美元符号“$”(M$, Micro$oft),意在讽刺该公司的商业主义。 ↩
-
RTFM是“Read The Fucking Manual”的缩写,意为“去读那该死的手册”,是一种非正式且无礼的表达。它常被用于呵斥提出初级问题的用户,让他们自己去寻找答案,是展现1990年代黑客文化排他性一面的用语。 ↩
-
这种不评价主张内容,而是基于其来源或动机来评判其价值的方式,属于“发生学谬误(genetic fallacy)”。(参考附录“案例2:发生学谬误”) ↩
-
不针对所提出的批评的合理性,而是质疑提出主张的个人的能力或资格,这属于“人身攻击谬误(ad hominem)”。(参考附录“案例1:人身攻击谬误”) ↩
-
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 ↩
-
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 ↩
-
“没有真正的苏格兰人”谬误(No True Scotsman Fallacy):由英国哲学家安东尼·弗卢(Antony Flew)命名的论证谬误。例如,当一人主张“苏格兰人不在粥里加糖”,而遭到“但我身边的苏格兰人就加糖”的反驳时,他便改口说:“‘真正的’苏格兰人才不会那样做”。这种通过用“真正的”这类任意标准来限定论证对象,从而从根本上回避反驳的企图,即是此谬误。 ↩
-
守门(Gatekeeping):指为成为特定群体的成员资格设立任意标准,并将不符合标准的人定义为“资格不够”而加以排斥的社会行为。 ↩