我们对 Rust 规范的愿景

2023 年 11 月 15 日 · Eric, Felix, Joel 和 Mara 代表 规范团队

几个月前,通过接受 RFC 3355,我们决定开始为 Rust 语言制定官方规范。Eric(Rust Reference 的维护者)、Felix(Rust 语言团队)、Joel(Rust 基金会)和 Mara(RFC 的作者)一直在共同努力启动这项工作。

通过这篇博文,我们将介绍我们迄今已采取的步骤,以及我们处理这项艰巨任务剩余部分的工作计划。

编辑

第一步是填补 RFC 中规定的“编辑”一职。规范的协调和编辑职责特意委托给 Rust 基金会,以确保工作的连续性。

作为基金会为此职位招聘工作的一部分,我们面试了几位感兴趣且具有相关经验的候选人。由于最终有一位候选人拒绝了录取通知,基金会选择考虑内部选项作为替代方案。基金会技术总监 Joel 毛遂自荐,愿意将此职位作为他现有工作的一部分。Eric、Felix 和 Mara 很快就同意让 Joel 担任规范编辑,因为他在行业标准和技术编辑方面拥有丰富的经验,并且与 Rust 项目关系密切。

规范团队

由于编辑不会独自完成这项工作,我们正在围绕规范工作组建一个团队,即规范团队,作为语言团队的一个子团队。

最初,其成员包括

  • Felix Klock (团队负责人)
  • Mara Bos (团队负责人)
  • Joel Marcey (团队成员,编辑)
  • Eric Huss (团队成员)

利益相关者

我们将选择并维护一份利益相关者名单,其中包括规范的专家和使用者,他们将担任顾问和审阅者。

最初,利益相关者将包括

  • Rust 语言团队的所有成员
  • 类型团队的一位或多位代表
  • 操作语义团队的一位或多位代表
  • Ferrocene(高保障/可用性,例如汽车行业)的一位或多位代表。
  • 形式化方法研发的一位或多位代表
  • 操作系统开发(Rust for Linux;Microsoft)的一位或多位代表

权限与审批

虽然规范团队负责编写和编辑规范,但 Rust 语言定义的权限仍属于相关团队,例如语言团队和库 API 团队。这些团队在必要时应邀请其他团队/子团队参与,例如提交问题、提名问题进行讨论以及对关键决策请求 FCP 批准。

为了让规范团队能够产出内容并进行迭代,而不被审批流程阻塞,我们将在我们的工作仓库中处理规范草案。借助一些工具,我们将公开跟踪哪些项目仍需要团队批准,以及哪些项目存在利益相关者的未决问题。

我们将所有变更分为次要变更和主要变更。次要变更是规范团队认为无争议或不重要的项目。例如,语言团队已通过 FCP 批准的变更、排版和语法修正、原意明确的澄清以及类似的不重要变更。主要变更是那些可能有疑问、重要或有争议的变更。规范团队、相关权威团队的任何成员以及任何规范利益相关者都可以将变更标记为主要变更。规范的主要变更必须经过通常的审批流程(例如语言 FCP),才能出现在已发布(非草案)版本的规范中。

语言团队和规范团队应努力拥有至少一名共享成员(例如 Felix),作为联络人,帮助确保我们对次要变更和主要变更的理解保持同步。

目标

规范团队的目标是创建和维护 Rust 规范。

Rust 规范的目的是提供一个权威资源,用于确定哪些源文本是有效的 Rust 程序,以及这些程序的行为方式。

理想的规范应 (1.) 为当前和未来版本的 Rust 程序的语义定义规定性界限,以及 (2.) 提供特定于该规范版本所对应 Rust 版本的语义描述性细节。

版本特定的细节可以直接在规范中提供,也可以通过委托给相关 Rust 团队拥有的其他文档间接提供。

术语解释

上面的措辞经过了仔细选择;值得对这些词语和整体措辞进行详细阐述

“定义”:规范的价值在于 (1.) 迫使作者定义事物,以及 (2.) 这些定义对规范读者的价值。

“语义”:Rust 具有静态语义和动态语义。Rust 的静态语义规定了哪些程序在语言中被接受,而动态语义决定了哪些被接受的程序是定义良好的,以及它们各自的含义。目的声明中的“语义”一词 collectively 指代 Rust 的静态语义和动态语义。

“当前和未来版本”:Rust 语言自诞生以来一直在演变,我们预计它将继续向前发展。这些演变步骤代表了对语言设计领域的遍历。对于每个 Rust 版本,我们期望当前的实现站在该领域的一个点上,而 Rust 社区的理想目标在更高的山上某个点等待。

Rust 的演变遵循两个轴:发布版本(例如 Rust 1.73)和版本(例如 2021 版本)。每个 Rust 版本都支持其最新的版本和所有先前的版本。规范将同步演变,规范的发布与 Rust 的发布相对应。规范的每个发布版本都定义了该 Rust 版本支持的所有版本的语义。

“规定性”、“描述性”:描述性词典试图描述一个词是如何使用的,而规定性词典则规定一个词应该如何使用。我们从这种区别中获得灵感,以区分两种重要的受众类型。

Rust 提供了一个稳定性承诺:“您永远不必担心升级到新的稳定版 Rust。”这自然引发了一个问题:为什么目的声明要区分跨版本的规定性定义和特定于版本的描述性定义?

我们的答案

描述性定义告诉读者一个构造在特定 Rust 版本(例如 Rust 1.76)下的行为方式。规定性界限告诉读者他们在未来 Rust 版本中可以和不能期望保持不变的内容。

我们将它们视为不同的概念,因为它们服务于两种不同的受众。

Rust 稳定性承诺的声明在项目认为的“担心”与“与 Rust 升级相关的合理工作量”方面留下了一些余地。在定义语义时,必须更明确地说明任何此类余地。一些 Rust 用户需要他们当前使用的 Rust 版本的预期语义描述;他们是版本特定细节的受众。但另一些 Rust 用户,例如某些库开发者,则具有更具前瞻性的视角。这些具有前瞻性的开发者可能需要保证,一个特定的代码片段 A 总是会被接受,并且总是具有特定的含义。他们可能需要保证,另一个片段 B 永远不会被接受。或者他们可能需要保证,利用 Unsafe Rust 的第三个片段 C 将始终具有未定义行为(例如,通过论证该转换未引入新的未定义行为来证明局部转换的合理性)。这些都属于需要对语义进行规定性定义的情况。对于这些开发者来说,说明当前 Rust 编译器做什么是不够的;他们需要知道未来版本的编译器可能做什么,这本质上是规定性的。

“界限”:从 Rust 用户的角度来看,理想的规范应提供既精确又具有规定性的定义。然而,项目目前在 Rust 语义的所有领域提供 100% 精确的规定性定义尚不成熟。

示例 1:Rust 的类型推断规则尚未准备好为所有未来版本固定下来。这些规则仍在开发中;今天被类型系统拒绝的健全程序明天可能被认为是可接受的。

示例 2:如果我们选择一个固定的文法,然后规定所有未来版本的 Rust 必须严格按照该文法将所有源输入分类为接受或拒绝,那么这将限制我们在文法中添加未来向后兼容的语言扩展的能力。

为了应对这些挑战,允许规定性定义的存在,我们牺牲了一些精确性以换取灵活性,计划将我们的规定性定义构建为语义的界限。

示例 3:Rust 内存模型仍然是一个开放的研究领域。我们尚未准备好为任意不安全代码建立健全/不健全的二元分类,并将其固定下来适用于所有未来版本的 Rust。

但是,有一些不安全代码模式是确定健全的;这可以作为定义不安全代码定义良好程度的下限的基础。同样,也存在一些确定不健全的不安全代码反模式;这可以作为定义在 Rust 动态语义中哪些不安全代码可能是定义良好的上限的基础(或者,从另一个角度来看:这些提供了在 Rust 中哪些不安全代码将始终被视为未定义行为的下限)。

规定性界限允许规范包含一个中间地带的程序,我们不承诺所有未来版本的 Rust 都会始终做出当前版本做出的相同决定。例如,届时可以规定性地说,给定的文法为所有未来版本的 Rust 必须接受的程序集合提供了一个下限,同时仍然允许语言以向后兼容的方式演变。也可以描述性地说,当前版本的 Rust 会拒绝不符合该文法的源输入。

随着规范的发展并变得更加精确,解决 Rust 语义中的歧义,上下界之间的差距将逐渐缩小。在极限情况下,当上界和下界相遇时,这个理想化的过程将产生一个完全精确的规定性定义。

在此期间,在我们达到该极限之前,规范将为静态语义和动态语义提供规定性界限和描述性细节。

“委托”:在许多广泛的领域,我们想要什么样的语义以及如何指定它们的问题都是开放的研究课题。这些领域的例子包括:macros 2.0、类型推断规则、trait 匹配规则以及不安全代码的操作语义。规范团队对此类主题声称拥有权威是不合理的。相反,将邀请其他团队贡献他们自己的详细描述,这些描述可以作为他们自己的文档发布,供规范引用。每个这样的文档都像规范本身一样,与特定的 Rust 版本相关联。此外,每个此类文档都类似于详细描述:通过委托产生的每个文档的范围旨在限制在特定的 Rust 版本。

如果贡献团队对适用于当前 Rust 版本之外的更广泛的规定性规则有建议,那么这应成为 Rust 规范文档本身的一部分。将这些规定性规则纳入文档应始终是规范团队的责任。所有此类规定性规则随后都需要经过规范审批流程。

增量开发

为当前和未来 Rust 版本提供规定性界限,并为当前 Rust 版本提供描述性细节,这是一项宏大的目标。我们将通过迭代和增量工作来最大化我们努力的价值。

我们期望规范的早期版本将重点关注提供当前 Rust 版本的详细描述。这样的规范可以大量借鉴现有成果,例如 Ferrocene 规范,因为它明确侧重于提供特定 Rust 版本的详细描述。对这些版本特定描述的反馈将帮助我们学习如何在规范中最好地构建规定性界限。

由于我们前面提到的对当前 Rust 版本的关注,规范的早期版本可能存在规定性界限比必要更不精确的空白。例如,规定“不安全 Rust 代码可能导致未定义行为”并未提供关于如何编写定义良好的不安全代码的指导。即使存在这种不精确性,规定性界限仍然可以提供有用的高层保证(例如,“安全 Rust 不会导致未定义行为”)。规范的未来版本将添加更多规定性细节(例如,“在以下条件下,不安全 Rust 代码不会导致未定义行为:……”),直到我们达到所需的精确度。

范围

规范应至少涵盖 Rust 语法和语义的以下领域。某些部分可能固有地与特定后端或目标实现技术相关联(例如 inline asm)。

  • Rust 的文法,通过巴科斯-诺尔范式 (BNF) 或其合理扩展来指定。
  • 宏展开
    • 示例宏 (macro_rules) 转录;卫生性
    • cfg 属性
    • 过程宏;属性和 derive
  • 路径和标识符解析
    • 模块
  • 静态语义
    • 类型定义;类型表达式;内存布局
    • 类型推断和类型检查;子类型
    • 生命周期和借用检查
  • 泛型;关联项解析和 Trait 解析
  • 安全 Rust 的操作语义
    • 绑定形式;match 表达式;drop glue
    • 值的移动和复制;借用
    • 字段投影;方法分派
    • 运算符重载
  • 不安全 Rust 的操作语义
    • 内存模型
    • 内联汇编
  • 常量求值
  • Crate 和 crate 链接

该列表可以随着时间推移而扩展。

呈现方式

Rust 规范将是一份公开文档,与所有其他 Rust 文档类似(并且具有相同的 MIT 许可条款 Apache 许可条款)。文本将以英文书写,并且只使用在规范本身中定义的技术术语,或者在可免费获得的在线词典中有明确定义的术语。

规范中的单个项目都可以被引用和命名:不仅通过超链接,也通过人类文本(例如“[syntax.patterns.arm.5]”)。在可能的情况下,这些项目名称/引用应在规范版本之间保持一致。

规范的迭代版本应包含突出显示版本之间差异的呈现方式。(例如,参见 Ada Reference Manual。)

Rust 规范将以鼓励志愿者贡献的格式进行维护,即使规范团队预计需要重写每项贡献,以保持规范语气的统一性。

虽然完整性和正确性是首要任务,但我们将尽力使规范尽可能易于理解。理想情况下,任何 Rust 程序员都应该能够深入其中,找到他们可能遇到的任何语言问题的答案,而无需咨询已经非常熟悉该文档的“语言律师”。

发布周期

Rust 的发布将独立于规范的审批流程。

如果某个发布版本的规范尚未获得批准,则该版本将在没有关联规范的情况下发布。(但是,我们仍然可能决定在稍后时间发布与给定版本相关联的规范。)

这是故意为之的。规范工作不得为项目增加新的障碍,以履行其现有义务,例如六周的发布周期。

我们的愿望是,我们最终将达到一个点,更新规范的发布是自动的,并且可以按照项目的六周发布周期进行。但是,短期和中期内,我们希望能够自由地落后于这个六周的发布周期。落后于 Rust 发布计划的能力在规范团队逐步为先前未涉及的领域添加新内容,或显著缩小当前版本规范中的规定性界限时,可能会特别有用。

虽然规范开发过程不会阻塞发布,但对语言特性的变更应与规范的相关更新耦合。一旦我们开始发布与特定版本耦合的规范,那么对当前规范中已记录的语言特性的变更,必须在规范团队批准对当前规范草案的相应 pull request 后才能稳定化。对规范中未记录的语言特性的变更,可以在不更新规范的情况下稳定化,但需要规范团队成员确认相应特性未记录。

通过强制执行这条规则,即新特性在稳定化之前必须成为规范的一部分,我们有望消除规范与 Rust 发布之间潜在延迟的主要来源。

下一步

现在我们已经完成了选择编辑、组建初始团队以及在这篇博文中记录我们的愿景这些初步步骤,接下来的步骤是

  • 为团队建立定期的会议安排。
  • 确定利益相关者名单。
  • 制作第一个“演示产品”,供利益相关者审阅。也就是说,建立我们的工具并选择一小部分 Rust 进行完整记录(包括交叉引用等),以准确地提供整个规范的初印象。