宣布 Rust 1.27

2018 年 6 月 21 日 · Rust 核心团队

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”的专业术语。假设 ab 都是 16 个元素长。每个元素都是一个 u8,这意味着每个切片都是 128 位数据。使用 SIMD,我们可以将 两个 ab 放入 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 而不是 itersimd_map 而不是 mapf32s(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 TraitTrait,因此感觉 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::cloneIterator::collectToOwned::to_owned 都将开始警告您是否未使用其结果,帮助您注意到您可能不小心丢弃的昂贵操作。

有关更多信息,请参阅详细的发布说明

库稳定化

此版本稳定了几个新的 API

有关更多信息,请参阅详细的发布说明

Cargo 功能

Cargo 在此版本中进行了两个小的升级。首先,它现在接受 --target-dir 标志,如果您想更改给定调用的目标目录。

此外,对 Cargo 处理目标的方式进行了一些调整。Cargo 将尝试自动发现项目中的测试、示例和二进制文件。但是,有时需要显式配置。但最初的实现存在一个问题:假设您有两个示例,并且 Cargo 正在发现它们。您想调整其中一个,因此您在 Cargo.toml 中添加了一个 [[example]] 来配置其设置。Cargo 目前看到您已经显式设置了一个,因此它不会尝试对其他示例进行任何自动检测。这非常令人惊讶。

因此,我们Cargo.toml 中添加了几个“auto”键。我们无法在不破坏可能无意中依赖它的项目的风险下修复此行为,因此,如果您想配置一些目标,但不想配置其他目标,可以在 [package] 部分将 autoexamples 键设置为 true

有关更多信息,请参阅详细的发布说明

1.27.0 的贡献者

许多人共同创建了 Rust 1.27。没有你们,我们无法做到。感谢!