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 书架 中的所有书籍现在都可搜索!例如,这里有“Rust 编程语言”中对“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 上运行速度非常快,但在不支持的 CPU 上仍然可以运行,尽管速度较慢。
如果所有这些看起来都比较低级且繁琐,那么它确实如此!std::arch
特别是用于构建这些类型的原语。我们希望将来最终稳定一个包含更高级功能的 std::simd
模块。但是现在推出基础知识可以让生态系统从今天开始尝试更高级的库。例如,查看faster 箱。以下是一个没有 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 箱已经添加了支持,并且新版本将包含这些 SIMD 加速,而您无需执行任何操作!
dyn Trait
Rust 的特征对象语法是我们最终感到遗憾的语法。如果您还记得,给定一个特征 Foo
,这是一个特征对象
Box<Foo>
但是,如果 Foo
是一个结构体,它将只是一个放在 Box<T>
中的普通结构体。在设计语言时,我们认为这里的相似性是一件好事,但经验表明它令人困惑。而且不仅仅是 Box<Trait>
情况;impl SomeTrait for SomeOtherTrait
在技术上也是有效的语法,但您几乎总是希望改为编写 impl<T> SomeTrait for T where T: SomeOtherTrait
。对于 impl SomeTrait
也是如此,它看起来像是要添加方法或可能是默认实现,但实际上是在特征对象中添加了内在方法。最后,随着最近添加了 impl Trait
语法,在解释事物时是 impl Trait
与 Trait
,因此感觉 Trait
是您应该使用的,因为它更短,但实际上并非总是如此。
因此,在 Rust 1.27 中,我们稳定了一个新语法,dyn 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 正在发现它们。您想调整其中一个,因此您在 Cargo.toml
中添加了一个 [[example]]
来配置其设置。Cargo 目前看到您已经显式设置了一个,因此它不会尝试对其他示例进行任何自动检测。这非常令人惊讶。
因此,我们在 Cargo.toml
中添加了几个“auto”键。我们无法在不破坏可能无意中依赖它的项目的风险下修复此行为,因此,如果您想配置一些目标,但不想配置其他目标,可以在 [package]
部分将 autoexamples
键设置为 true
。
有关更多信息,请参阅详细的发布说明。
1.27.0 的贡献者
许多人共同创建了 Rust 1.27。没有你们,我们无法做到。感谢!