阅读 1934

Rust 官方发布 2021 的规划

我们很高兴地宣布,Rust语言的第三版,即Rust 2021,计划在10月发布。 Rust 2021包含一些小的变化,但预计会对Rust在实践中的感觉有重大改进。

什么是版本?

Rust 1.0的发布确立了"稳定而不停滞 "作为Rust的核心交付价值。 自1.0发布以来,Rust的规则是,一旦一个功能被发布到稳定版,我们就会承诺在未来的所有版本中支持该功能。

然而,有些时候,能够对语言做一些不向后兼容的小改动是很有用的。 最明显的例子是引入一个新的关键字,这将使同名的变量失效。 例如,Rust的第一个版本没有asyncawait 关键字。在后来的版本中突然将这些词改为关键字,会破坏像let async = 1;

版本是我们用来解决这个问题的机制。 当我们想要发布一个否则会向后不兼容的功能时,我们会将其作为新的Rust_版本的_一部分。 版本是可选择的,因此现有的板块在明确迁移到新版本之前不会看到这些变化。 这意味着,即使是最新版本的Rust仍然_不会_将async 作为一个关键字,除非选择2018版或更高版本。这个选择是作为_每个板块_ 的一部分Cargo.toml 。由cargo new 创建的新板块总是被配置为使用最新稳定版本。

版本是不会分裂生态系统

版本最重要的规则是,一个版本的cockate可以与其他版本编译的cockate进行无缝的互操作。这确保了迁移到一个较新的版本的决定是一个 "私人决定",该板块可以在不影响其他板块的情况下做出。

对板条箱互操作性的要求意味着我们可以在一个版本中做一些限制。 一般来说,在一个版本中发生的变化往往是 "皮毛 "的。 所有的Rust代码,无论版本如何,最终都会被编译成编译器中相同的内部表示。

版本迁移很容易,而且基本上是自动化的

我们的目标是让crack很容易升级到新的版本。 当我们发布新的版本时,我们也提供工具来自动迁移。 它对你的代码进行必要的小改动,使其与新的版本兼容。 例如,当迁移到Rust 2018时,它改变了任何名为async ,以使用等效的原始标识符语法r#async

自动迁移不一定是完美的:可能会有一些角落的情况,仍然需要手动修改。 该工具努力避免改变语义,以免影响代码的正确性或性能。

除了工具化,我们还维护了一个版本迁移指南,涵盖了一个版本的变化。 这个指南将描述变化,并给出人们可以了解更多信息的指针。 它还将涵盖人们应该注意的任何角落案例或细节。 该指南既可以作为版本的概述,也可以在人们遇到自动化工具化问题时作为快速故障排除参考。

Rust 2021 计划有哪些变化?


在过去的几个月里,Rust 2021工作组审议了许多关于新版本中包含哪些内容的建议。 我们很高兴地宣布最终的版本变化清单。 每个功能都必须满足两个标准才能进入这个清单。 首先,它们必须得到相关Rust团队的批准。 其次,它们的实施必须足够深入,我们有信心在计划的里程碑前及时完成。

对前奏的补充

标准库的前奏是包含每个模块中自动导入的所有东西的模块。它包含常用的项目,如OptionVecdropClone

Rust编译器会优先考虑任何手动导入的项目,而不是来自前奏的项目,以确保对前奏的添加不会破坏任何现有的代码。例如,如果你有一个名为example 的板块或模块,包含一个pub struct Option; ,那么use example::*; 将使Option 明确地引用来自example ;而不是来自标准库。

然而,在前奏中添加_特质_会以一种微妙的方式破坏现有的代码。如果std'sTryInto 也被导入,那么使用MyTryInto 特质对x.try_into() 的调用可能会变得含糊不清,无法编译,因为它提供了一个同名的方法。这就是我们还没有在前奏中添加TryInto 的原因,因为有很多代码会这样破坏。

作为一个解决方案,Rust 2021将使用一个新的前奏。 它与当前的前奏相同,只是新增加了三个内容。

默认的Cargo特性解析器

自Rust 1.51.0以来,Cargo对一个新的特性解析器提供了选择支持,可以通过resolver = "2"Cargo.toml

从Rust 2021开始,这将是默认的。也就是说,在Cargo.toml 中写入edition = "2021" 将意味着resolver = "2"

新的特性解析器不再合并以多种方式依赖的crates的所有请求的特性。 详情见Rust 1.51的公告

用于数组的IntoIterator

直到Rust 1.53,只有对数组的_引用_实现了IntoIterator 。这意味着你可以在&[1, 2, 3]&mut [1, 2, 3] ,但不能直接在[1, 2, 3]

for &e in &[1, 2, 3] {} // Ok :)

for e in [1, 2, 3] {} // Error :(
复制代码

这是一个长期存在的问题,但解决方案并不像看起来那么简单。仅仅添加特质实现就会破坏现有的代码。今天,array.into_iter() 已经可以编译了,因为根据方法调用语法的工作原理,它隐含地调用了(&array).into_iter() 。添加特质实现将改变其含义。

通常我们把这种类型的破坏(添加特性实现)归类为 "轻微的 "和可以接受的。 但在这种情况下,有太多的代码会被破坏。

有人多次建议 "只在Rust 2021中为数组实现IntoIterator "。然而,这根本不可能。你不可能让一个特质的实现存在于一个版本,而不存在于另一个版本,因为版本可以混合。

相反,我们决定在_所有_版本中添加特质实现(从Rust 1.53.0开始),但添加一个小黑客,以避免在Rust 2021之前出现故障。 在Rust 2015和2018的代码中,编译器仍然会像以前一样将array.into_iter()解析为(&array).into_iter() ,就像特质实现不存在一样。这_只_适用于.into_iter() 方法调用语法。它不影响任何其他语法,如for e in [1, 2, 3]iter.zip([1, 2, 3])IntoIterator::into_iter([1, 2, 3]) 。 这些将在_所有_版本中开始工作。

虽然这需要一个小的黑客来避免破坏,这是一个耻辱,但我们对这个解决方案将版本之间的差异保持在绝对最小的程度感到非常满意。 因为这个黑客只存在于旧版本中,在新版本中没有增加复杂性。

闭包中的不连续捕获

闭包会自动捕获你在其主体中引用的任何东西。例如,|| a + 1 会自动捕获周围环境中对a 的引用。

目前,这适用于整个结构,即使只使用一个字段。例如,|| a.x + 1 捕获对a 的引用,而不仅仅是a.x 。 在某些情况下,这是一个问题。当结构的一个字段已经被借用(可变)或移出时,其他字段不能再用于闭包,因为这将捕获整个结构,这不再可用。

let a = SomeStruct::new();

drop(a.x); // Move out of one field of the struct

println!("{}", a.y); // Ok: Still use another field of the struct

let c = || println!("{}", a.y); // Error: Tries to capture all of `a`
c();
复制代码

从Rust 2021开始,闭包将只捕获它们使用的字段。 因此,上面的例子在Rust 2021中可以正常编译。

这种新的行为只在新版本中激活,因为它可以改变字段被丢弃的顺序。 至于所有版本的变化,自动迁移是可用的,它将更新你的闭包,这一点很重要。它可以在闭包内插入let _ = &a; ,强制整个结构像以前一样被捕获。

panic!() 宏的一致性

panic!() 宏是 Rust 最知名的宏之一。然而,它有一些微妙的惊喜,由于向后兼容,我们不能随便改变。

panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Ok, panics with the message "{}"
复制代码

panic!() 宏只在调用一个以上的参数时使用字符串格式化。 当调用一个参数时,它甚至不看那个参数。

let a = "{";
println!(a); // Error: First argument must be a format string literal
panic!(a); // Ok: The panic macro doesn't care
复制代码

(它甚至接受非字符串,如panic!(123) ,这是不常见的,而且很少有用)。

一旦隐式格式参数稳定下来,这将是一个特别的问题。这个特性将使println!("hello {name}") 成为println!("hello {}", name) 的简称。然而,panic!("hello {name}") 不会像预期的那样工作,因为panic!() 不会将单个参数作为格式字符串处理。

为了避免这种混乱的情况,Rust 2021采用了更加一致的panic!() 宏。新的panic!() 宏将不再接受任意表达式作为唯一的参数。它将和println!() 一样,总是将第一个参数处理为格式字符串。 由于panic!() 将不再接受任意的有效载荷。panic_any() 将是用格式化字符串以外的东西进行恐慌的唯一方法。

此外,在Rust 2021中,core::panic!()std::panic!() 将是相同的。目前,这两者之间存在一些历史差异,在打开或关闭#![no_std] 时,会有明显的差异。

保留语法

为了给未来一些新的语法腾出空间,我们决定为前缀标识符和字面意义保留语法:prefix#identifier,prefix"string",prefix'c', 和prefix#123, 其中prefix 可以是任何标识符。(除了那些已经有意义的,如b'…'r"…"

这是一个突破性的变化,因为目前宏可以接受hello"world" ,他们会将其视为两个单独的标记:hello"world" 。不过,(自动)修复很简单。只要插入一个空格:hello "world"

除了将这些变成标记化错误外,RFC还没有给任何前缀附加含义。 给特定前缀分配含义是留给未来的建议的,由于现在保留了这些前缀,所以不会有破坏性变化。

这些是你在未来可能看到的一些新的前缀。

  • f"" ,作为一个格式字符串的缩写。例如,f"hello {name}" ,作为相当于format_args!() 的调用的缩写。

  • c""z"" 表示空尾的 C 字符串。

  • k#keyword ,以允许编写在当前版本中尚不存在的关键字。例如,虽然async 在2015版中不是一个关键字,但这个前缀将允许我们在2015版中接受k#async 作为替代,同时我们等待2018版将async 保留为一个关键字。

将两个警告提升为硬错误

在Rust 2021中,两个现有的行文将成为硬错误。 这些行文在旧版本中仍是警告。

  • bare-trait-objects: 在Rust 2021中,使用dyn 关键字来识别特质对象将是强制性的。

  • ellipsis-inclusive-range-patterns :Rust 2021中不再接受已被废弃的... ,该语法用于包容性范围模式。它已被..= 所取代,该语法与表达式一致。

Or patterns in macro_rules

从Rust 1.53.0开始,模式被扩展到支持| ,嵌套在模式的任何地方。这使得你可以写Some(1 | 2) ,而不是Some(1) | Some(2) 。由于以前根本不允许这样做,这不是一个突破性的变化。

然而,这个变化也影响到了macro_rules 宏。这种宏可以接受使用:pat 片段指定符的模式。目前,:pat _不_匹配| ,因为在Rust 1.53之前,不是所有的模式(在所有的嵌套层)都可以包含一个| 。接受类似A | B 的模式的宏,如 matches!() $($_:pat)|+因为我们不想破坏任何现有的宏,所以我们_没有_在Rust 1.53.0中改变:pat 的含义以包括|

相反,我们将作为Rust 2021的一部分进行修改。在新版本中,:pat 片段指定器_将_匹配A | B

由于有时人们仍然希望在没有| 的情况下匹配单一的模式变体,所以增加了指定的片段:pat_param ,以保留旧的行为。这个名字是指它的主要使用情况:封闭参数中的模式。

下一步是什么?

我们的计划是在9月前完成这些修改的合并和全面测试,以确保2021年的版本能够进入Rust 1.56.0。

然而,请注意,Rust是一个由志愿者运营的项目。 我们优先考虑在Rust上工作的每个人的个人福祉,而不是我们可能设定的任何最后期限和期望。 这可能意味着在必要时推迟一个版本,或者放弃一个被证明太难或压力太大而不能及时完成的功能。

也就是说,我们正在按计划进行,许多困难的问题已经被解决了,这要感谢所有为Rust 2021做出贡献的人!💛


你可以期待7月份关于新版本的另一个公告。 届时,我们希望所有的变化和自动迁移都能实现,并准备好进行公开测试。

我们将很快在 "Inside Rust "博客上发布一些关于这个过程的细节和被拒绝的建议。

如果你真的等不及了,许多功能已经可以在 Rust Nightly上使用`-Zunstable-options --edition=2021`。

文章分类
后端
文章标签