Rust 团队很高兴地宣布 Rust 的新版本 1.27.0。Rust 是一种专注于安全性、速度和并发性的系统编程语言。
如果您通过 rustup 安装了以前版本的 Rust,则获取 Rust 1.27.0 非常简单,只需执行以下命令:
$ rustup update stable
如果您尚未安装,可以从我们网站上的相应页面获取 rustup
,并查看 GitHub 上1.27.0 的详细发行说明。
此外,我们想提请注意一件事:在 1.27.0 发布之前,我们在 1.26.0 中引入的“默认匹配绑定”功能中发现了一个错误,可能会引入不健全性。由于该问题在发布过程的后期才被发现,并且从 1.26.0 版本就已存在,我们决定坚持我们的发布周期模型。我们预计很快会发布 1.27.1 来修复此问题,如果需要,也可能会发布 1.26.3。有关此处具体信息的更多内容将在该版本的发布公告中提供。
1.27.0 稳定版的新增功能
此版本具有两个人们期待已久的重大语言功能。但首先,关于文档的一些说明:Rust 书架中的所有书籍现在都可搜索了!例如,这是对“The Rust Programming Language”中 'borrow' 的搜索。这将有助于更容易地找到您正在寻找的内容。此外,还有一本新书:rustc
手册。本书解释了如何直接使用 rustc
,以及一些其他有用的信息,例如所有 lint 的列表。
SIMD
好的,现在来说重点:SIMD 的基础知识现在可以使用了!SIMD 代表“单指令,多数据”。考虑以下函数:
pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) {
for ((a, b), c) in a.iter().zip(b).zip(c) {
*c = *a + *b;
}
}
在这里,我们获取两个切片,并将数字相加,将结果放在第三个切片中。最简单的方法是完全按照代码所做的那样,循环遍历每一组元素,将它们相加,然后将其存储在结果中。但是,编译器通常可以做得更好。LLVM 通常会“自动向量化”这样的代码,这是一个表示“使用 SIMD”的奇特术语。假设 a
和 b
都是 16 个元素长。每个元素都是一个 u8
,这意味着每个切片将是 128 位数据。使用 SIMD,我们可以将 a
和 b
都放入 128 位寄存器中,在 *单个*
指令中将它们相加,然后将结果的 128 位复制到 c
中。那会快得多!
虽然稳定的 Rust 一直能够利用自动向量化,但有时,编译器不够智能,无法意识到我们可以这样做。此外,并非每个 CPU 都具有这些功能,因此 LLVM 可能不会使用它们,以便您的程序可以在各种硬件上使用。因此,在 Rust 1.27 中,std::arch
模块的添加使我们可以直接使用这些指令,这意味着我们无需依赖智能编译器。此外,它还包括一些功能,可以让我们根据各种条件选择特定的实现。例如:
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "avx2"))]
fn foo() {
#[cfg(target_arch = "x86")]
use std::arch::x86::_mm256_add_epi64;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::_mm256_add_epi64;
unsafe {
_mm256_add_epi64(...);
}
}
在这里,我们使用 cfg
标志根据我们所定位的机器选择正确的版本;在 x86
上,我们使用该版本,在 x86_64
上,我们使用其版本。我们也可以在运行时选择:
fn foo() {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if is_x86_feature_detected!("avx2") {
return unsafe { foo_avx2() };
}
}
foo_fallback();
}
在这里,我们有两个版本的函数:一个使用 AVX2
,这是一种特定的 SIMD 功能,可让您进行 256 位操作。is_x86_feature_detected!
宏将生成代码来检测您的 CPU 是否支持 AVX2,如果支持,则调用 foo_avx2
函数。如果不支持,则我们回退到非 AVX 实现 foo_fallback
。这意味着我们的代码将在支持 AVX2 的 CPU 上超快运行,但在不支持 AVX2 的 CPU 上仍然可以工作,尽管速度较慢。
如果所有这些看起来有点底层和繁琐,那么,确实如此!std::arch
专门用于构建此类事物的原语。我们希望最终在未来稳定一个具有更高级内容的 std::simd
模块。但是现在发布基础知识可以让生态系统从今天开始尝试更高级别的库。例如,查看 faster crate。这是一个没有 SIMD 的代码片段:
let lots_of_3s = (&[-123.456f32; 128][..]).iter()
.map(|v| {
9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0
})
.collect::<Vec<f32>>();
要通过 faster
在此代码中使用 SIMD,您可以将其更改为:
let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter()
.simd_map(f32s(0.0), |v| {
f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0)
})
.scalar_collect();
它看起来几乎相同:simd_iter
代替 iter
,simd_map
代替 map
,f32s(2.0)
代替 2.0
。但是您会得到一个为您生成的 SIMD 版本。
除此之外,您可能永远不会自己编写任何这些代码,但与往常一样,您所依赖的库可能会这样做。例如,regex crate 已经添加了支持,并且新版本将包含这些 SIMD 加速,而您无需执行任何操作!
dyn Trait
Rust 的 trait 对象语法是我们最终感到后悔的语法之一。如果您还记得,给定一个 trait Foo
,这是一个 trait 对象:
Box<Foo>
但是,如果 Foo
是一个结构体,它只是一个放置在 Box<T>
中的普通结构体。在设计该语言时,我们认为此处的相似之处是一件好事,但是经验表明,这会让人感到困惑。而且不仅仅是 Box<Trait>
的情况;impl SomeTrait for SomeOtherTrait
在技术上也是有效的语法,但是您几乎总是想写 impl<T> SomeTrait for T where T: SomeOtherTrait
。与 impl SomeTrait
相同,它看起来会添加方法或可能的默认实现,但实际上会向 trait 对象添加固有方法。最后,随着最近添加的 impl Trait
语法,在解释事物时会出现 impl Trait
与 Trait
的情况,因此感觉 Trait
是您应该使用的语法,因为它更短,但实际上并非总是如此。
因此,在 Rust 1.27 中,我们稳定了一个新语法:dyn Trait
。trait 对象现在看起来像这样:
// old => new
Box<Foo> => Box<dyn Foo>
&Foo => &dyn Foo
&mut Foo => &mut dyn Foo
对于其他指针类型,Arc<Foo>
现在是 Arc<dyn Foo>
等。由于向后兼容性,我们无法删除旧语法,但是我们包含了一个 lint,默认设置为允许,称为 bare-trait-object
。如果您想对旧语法进行 lint,可以将其打开。我们认为,目前默认情况下打开它会引发太多警告。
顺便说一句,我们正在开发一个名为
rustfix
的工具,该工具可以自动将您的代码升级到较新的习惯用法。它使用这些类型的 lint 来实现此目的。请期待在未来的公告中听到有关rustfix
的更多信息。
#[must_use]
函数上的 最后,#[must_use]
属性正在升级:现在可以将其用于函数。
以前,它仅适用于类型,例如 Result<T, E>
。但是现在,您可以这样做:
#[must_use]
fn double(x: i32) -> i32 {
2 * x
}
fn main() {
double(4); // warning: unused return value of `double` which must be used
let _ = double(4); // (no warning)
}
我们还增强了标准库的几个部分来利用这一点;如果您不使用 Clone::clone
、Iterator::collect
和 ToOwned::to_owned
的结果,它们都将开始发出警告,帮助您注意到可能因意外抛弃而产生的昂贵操作。
有关更多信息,请参阅详细发行说明。
库稳定性
此版本稳定了几个新的 API:
DoubleEndedIterator::rfind
DoubleEndedIterator::rfold
DoubleEndedIterator::try_rfold
Duration::from_micros
Duration::from_nanos
Duration::subsec_micros
Duration::subsec_millis
HashMap::remove_entry
Iterator::try_fold
Iterator::try_for_each
NonNull::cast
Option::filter
String::replace_range
Take::set_limit
hint::unreachable_unchecked
os::unix::process::parent_id
process::id
ptr::swap_nonoverlapping
slice::rsplit_mut
slice::rsplit
slice::swap_with_slice
有关更多信息,请参阅详细发行说明。
Cargo 功能
此版本 Cargo 有两个小的升级。首先,如果您想更改给定调用的目标目录,它现在采用 --target-dir
标志。
此外,对 Cargo 处理目标的方式进行了一些调整。Cargo 将尝试自动发现项目中的测试、示例和二进制文件。但是,有时需要显式配置。但是初始实现存在一个问题:假设您有两个示例,并且 Cargo 正在发现它们。您想调整其中一个,因此您将 [[example]]
添加到 Cargo.toml
中以配置其设置。Cargo 当前会看到您已显式设置了一个,因此不会尝试对其他示例进行任何自动检测。这非常令人惊讶。
因此,我们在 Cargo.toml
中添加了几个“auto”键。我们无法在不破坏可能无意中依赖它的项目的情况下修复此行为,因此,如果您想配置某些目标,但不配置其他目标,则可以在 [package]
部分中将 autoexamples
键设置为 true
。
有关更多信息,请参阅详细发行说明。
1.27.0 的贡献者
许多人齐心协力创建了 Rust 1.27。没有你们,我们不可能做到。谢谢!