几个月前,通过接受 RFC 3355,决定开始制定 Rust 语言的官方规范。Eric(Rust 参考的维护者)、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 版本定义给定 Rust 程序语义的规定性界限,又(2.)提供特定于 Rust 版本并与该规范实例耦合的语义的描述性细节。
版本特定的详细信息可以直接在规范中提供,也可以通过委托给相关 Rust 团队拥有的其他文档间接提供。
术语解释
上面的词语是经过仔细选择的;值得详细说明这些词语和整体措辞
“定义”:规范的效用来自于(1.)迫使作者定义事物和(2.)这些定义对规范读者的价值。
“语义”:Rust 具有静态语义和动态语义。Rust 的静态语义规定了哪些程序被语言接受,而动态语义确定了哪些被接受的程序是定义明确的,以及它们各自的含义。“语义”在目的声明中指的是 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 语义中的歧义。在极限情况下,当/如果上限和下限相遇时,这个理想化的过程会产生一个完全精确的规定性定义。
在此期间,在我们达到该限制之前,规范将为静态和动态语义提供规范性的界限和描述性的细节。
“委托”:在许多领域,我们想要的语义是什么,以及如何指定这些语义,都是开放的研究课题。这些领域的例子包括:宏 2.0、类型推断规则、trait 匹配规则以及不安全代码的操作语义。规范团队声称在这些主题上拥有权威是不合理的。相反,将邀请其他团队贡献他们自己的详细描述,这些描述可以作为他们自己的文档发布,规范可以参考这些文档。每个这样的文档,就像规范本身一样,都与特定的 Rust 版本相关联。此外,每个这样的文档都类似于详细描述:通过委托生成的每个文档的范围都旨在限制为特定的 Rust 版本。
如果贡献团队对当前 Rust 版本之外应保持的更广泛的规范性规则有意见,那么这些规则应成为 Rust 规范文档本身的一部分。此类规范性规则始终应由规范团队负责纳入文档中。所有此类规范性规则都将接受规范批准流程。
增量开发
为当前和未来的 Rust 版本提供规范性的界限,并为当前 Rust 版本提供描述性的细节,这两者都极具挑战性。我们将通过迭代和增量的方式工作来最大化我们工作的价值。
我们预计规范的早期版本将主要侧重于提供当前 Rust 版本的详细描述。这样的规范可以很大程度上从现有工作成果(如 Ferrocene 规范)中派生,因为它明确侧重于提供特定 Rust 版本的详细描述。对这些特定于版本的描述的反馈将帮助我们了解如何在规范中最好地制定规范性界限。
由于我们上述对当前 Rust 版本的关注,规范的早期版本可能会存在一些差距,在这些差距中,规范性界限可能比必要的更不精确。例如,规定“不安全的 Rust 代码可能会导致未定义的行为”并不能为如何编写定义良好的不安全代码提供指导。即使存在这种不精确性,规范性界限仍然可以提供有用的高层次保证(例如,“安全的 Rust 不会导致未定义的行为”)。规范的未来版本将添加更多规范性细节(例如,“不安全的 Rust 代码在以下条件下不会导致未定义的行为:...”),直到我们达到我们期望的精确度。
范围
规范应至少涵盖 Rust 语法和语义的以下领域。某些部分可能本质上与特定的后端或目标实现技术相关联(例如,内联汇编)。
- Rust 的语法,通过巴科斯-瑙尔范式 (BNF) 或 BNF 的一些合理扩展来指定。
- 宏展开
- 示例宏 (
macro_rules
) 的转录;卫生性 cfg
属性- 过程宏;属性和派生
- 示例宏 (
- 路径和标识符解析
- 模块
- 静态语义
- 类型定义;类型表达式;布局
- 类型推断和类型检查;子类型化
- 生命周期和借用检查
- 泛型;关联项解析和 Trait 求解
- 安全 Rust 的操作语义
- 绑定形式;匹配表达式;drop 胶水
- 值的移动和复制;借用
- 字段投影;方法分派
- 运算符重载
- 不安全 Rust 的操作语义
- 内存模型
- 内联汇编
- 常量求值
- Crate 和 crate 链接
此列表可以随着时间的推移而扩展。
呈现
Rust 规范将是一个公开访问的文档,类似于所有其他 Rust 文档(并且具有相同的许可 条款)。文本将用英文书写,并且只会使用规范本身中定义的或在免费在线词典中具有明确定义的技术术语。
规范中的单个条目可以被引用和命名:不仅在超链接中,而且在人类文本中(例如,“[syntax.patterns.arm.5]”)。如果可能,这些条目名称/引用应在规范的各个版本中保持不变。
规范的迭代版本应包括突出显示版本之间差异的渲染。(请参阅例如 Ada 参考手册。)
Rust 规范将以一种鼓励志愿者贡献的格式维护,即使规范团队期望为了保持规范一致的语气而必须重新编写每个贡献。
虽然完整性和正确性是首要优先事项,但我们将尽力使规范尽可能易于访问。理想情况下,任何 Rust 程序员都应该能够深入研究并找到他们可能有的任何语言问题的答案,而无需询问已经非常熟悉该文档的“语言律师”。
发布节奏
Rust 的发布将独立于规范批准流程进行。
如果某个发布版本没有获得批准的规范,则该发布版本将在没有关联规范的情况下发布。(但是,我们可能仍会决定在稍后时间交付与给定发布版本相关的规范。)
这是刻意设计的。规范工作绝不能为项目为满足其现有义务(例如 6 周发布节奏)而增加新的障碍。
我们的愿望是,我们最终会达到一个可以根据项目 6 周发布节奏自动交付更新规范的地步。但是,在短期和中期,我们希望能够自由地落后于 6 周的发布节奏。当规范团队正在为以前未解决的领域逐步添加新内容,或者在当前版本的规范中显着缩小规范性界限时,能够落后于 Rust 发布时间表可能会特别有用。
虽然规范开发过程不会阻止发布,但对语言功能的更改应与规范的相关更新相结合。一旦我们开始发布与特定版本相关的规范,那么对当前规范中记录的语言功能的更改,如果没有规范团队对当前规范草案的批准的相应拉取请求,则不能稳定。对规范中未记录的语言功能的更改可以稳定,无需更新规范,但需要规范团队成员承认相应的特性未被记录。
通过强制执行新功能必须在稳定之前成为规范一部分的规则,我们希望消除规范和 Rust 发布之间潜在滞后的主要来源。
下一步
既然我们已经采取了选择编辑器、组建初始团队并在本博客文章中记录我们的愿景的初步步骤,那么下一步是
- 为团队设置定期会议时间表。
- 建立利益相关者列表。
- 制作第一个“演示产品”,供利益相关者审查。也就是说,设置我们的工具并选择一小部分 Rust 来完整记录(包括交叉引用等),以准确地初步了解完整规范的外观。