发布 Rust 1.16

2017 年 3 月 16 日 · Rust 核心团队

Rust 团队很高兴宣布 Rust 的最新版本 1.16.0。Rust 是一门专注于安全、速度和并发性的系统编程语言。

如果您已经安装了旧版本的 Rust,获取 Rust 1.16 非常简单,只需运行

$ rustup update stable

如果您尚未安装,可以从我们的网站上找到合适的页面获取 rustup,并在 GitHub 上查看 1.16.0 的详细发行说明

1.16.0 stable 版本包含什么

Rust 1.16 中最大的新增功能是 cargo check。这个新的子命令应该在许多情况下加快开发工作流程。

它有什么作用?让我们退一步,谈谈 rustc 如何编译您的代码。编译有很多“趟”(passes),也就是说,编译器从源代码到生成最终二进制文件需要许多不同的步骤。您可以通过向 nightly 编译器传递 -Z time-passes 参数来查看这些步骤(以及它们花费的时间和内存)

rustc +nightly hello.rs -Z time-passes
time: 0.003; rss: 16MB  parsing
time: 0.000; rss: 16MB  recursion limit
time: 0.000; rss: 16MB  crate injection
time: 0.000; rss: 16MB  plugin loading
time: 0.000; rss: 16MB  plugin registration
time: 0.049; rss: 34MB  expansion
<snip>

步骤很多。但是,您可以将这个过程视为两个大步骤:首先,rustc 执行所有安全检查,确保您的语法正确,等等。其次,一旦它确定一切就绪,它就生成最终的二进制代码,也就是您最终执行的代码。

事实证明,第二步花费大量时间。而且大多数情况下,它不是必需的。也就是说,当您在编写 Rust 代码时,许多开发人员会采用这样的工作流程

  1. 编写代码。
  2. 运行 cargo build 确保其编译通过。
  3. 根据需要重复步骤 1-2。
  4. 运行 cargo test 确保测试通过。
  5. 返回步骤 1。

在步骤二中,您实际上从未运行代码。您正在寻求编译器的反馈,而不是真正运行二进制文件。cargo check 正是支持这种情况:它运行所有编译器的检查,但不生成最终的二进制文件。

那么您实际上能获得多少加速呢?与大多数性能相关的问题一样,答案是“视情况而定”。以下是一些非常不科学的基准测试结果

项目cargodiesel
首次 build134.75s236.78s15.27s
首次 check50.88s148.52s12.81s
提速2.6481.5941.192
二次 build15.97s64.34s13.54s
二次 check2.9s9.29s12.3s
提速5.5066.9251.100

“首次”(initial)类别指的是克隆项目后的第一次构建。“二次”(secondary)类别涉及在 src\lib.rs 文件顶部添加一行空行并再次运行命令。这就是为什么首次构建的提速更显著;它们还包括对所有依赖项以及 crate 本身的检查。正如您所见,依赖项多的大型项目看到了很大的改进,而小型项目的收益则较为温和。

我们也在努力整体上改进编译时间,尽管目前还没有特别需要强调的内容。

其他改进

为了支持 cargo checkrustc 学会了生成一种新类型的文件:.rmeta。这个文件只包含关于特定 crate 的元数据。cargo check 需要这些信息来检查依赖项的类型等。它也对 Rust Language Server 以及未来可能出现的更多工具非常有用。

另一个重要的变化是移除了一个长期存在的诊断提示:consider using an explicit lifetime parameter。当您有不正确的生命周期注解,并且编译器认为您可能想表达别的意思时,就会出现此诊断提示。请考虑以下代码

use std::str::FromStr;

pub struct Name<'a> {
    name: &'a str,
}

impl<'a> FromStr for Name<'a> {
    type Err = ();

    fn from_str(s: &str) -> Result<Name, ()> {
        Ok(Name { name: s })
    }
}

在这里,Rust 不确定如何处理生命周期;按照代码的写法,无法保证 s 的生命周期与 Name 一样长,而 Name 的有效性需要这个条件。我们尝试使用 Rust 1.15.1 编译这段代码

$ rustc +1.15.1 foo.rs --crate-type=lib
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in generic type due to conflicting requirements
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here
   |
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name, ()>
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here

编译器解释了问题,并给出了一个有用的建议。那我们试试看:修改代码,加入 'a,然后再次编译

$ rustc +1.15.1 .\foo.rs --crate-type=lib
error[E0308]: method not compatible with trait
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &'a str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here: lifetime mismatch
   |
<snip>
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name<'a>, ()>
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &'a str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here

还是不行。这个帮助信息实际上没什么帮助。它确实建议添加另一个生命周期,这次是在 Name 上。如果我们这样做...

$ rustc +1.15.1 .\foo.rs --crate-type=lib
<snip>
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name<'a>, ()>
  --> .\foo.rs:10:5

... 编译器啊,这不就是我们已经有的吗!

这个诊断提示是出于好意,但出错时,它的错误是非常严重的,正如您在这里看到的。有时它甚至不会建议有效的 Rust 语法!此外,更高级的 Rust 用户并不真正需要这个建议,但新的 Rustaceans 会认真对待它们,然后走上这条错误的道路。因此,我们决定目前完全删除这个帮助信息。未来我们可能会重新引入它,但前提是我们能够限制误报。

顺带一提:上述实现是不可行的;Name 需要使用 String,而不是 &str

在其他诊断变化方面,之前的 Rust 版本会尝试提供有用的建议来修复拼写错误

let foo = 5;

println!("{}", ffo);

会给出这个错误

error[E0425]: cannot find value `ffo` in this scope
 --> foo.rs:4:20
  |
4 |     println!("{}", ffo);
  |                    ^^^ did you mean `foo`?

然而,这只在某些情况下发生:有时在局部变量中,以及结构体字段中。现在这几乎可以在任何地方发生。当结合一些其他相关改进时,这类诊断的准确性有了显著提高。

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

标准库稳定化

本次发布有 21 个新的 API 得到了稳定

此外,现有的一些函数也得到了一些小的改进。例如 writeln! 现在也有了一个单参数形式,就像 println! 一样。虽然最终只是写入一个换行符,但这是一种很好的对称性。

标准库中的所有结构体现在都实现了 Debug trait

当对 &str 进行切片时,您会看到更好的错误提示。例如,这段代码

&"abcαβγ"[..4]

是不正确的。它会生成这个错误

thread 'str::test_slice_fail_boundary_1' panicked at 'byte index 4 is not
a char boundary; it is inside 'α' (bytes 3..5) of `abcαβγ`'

分号 ; 后面的部分是新增的。

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

Cargo 特性

除了 cargo check,Cargo 和 crates.io 还增加了一些新的亮点。例如,cargo buildcargo doc 现在支持 --all 标志,可以用一个命令构建和生成工作区中所有 crate 的文档。

Cargo 现在新增了 --version --verbose 标志,与 rustc 保持一致。

Crates.io 现在可以在您的 crate 页面上展示您的 TravisCI 或 AppVeyor 徽章

此外,Cargo 和 crates.io 都理解类别(categories)。与自由形式的关键词(keywords)不同,类别是经过组织的。另外,关键词用于搜索,而类别不是。换句话说,类别旨在帮助浏览,而关键词旨在帮助搜索。

您可以在这里按类别浏览 crate。

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

1.16.0 的贡献者

上一个版本,我们推出了thanks.rust-lang.org。我们一直在进行一些幕后的重构工作,以便支持更多项目而不仅仅是 Rust 本身;我们希望在下一次发布中推出这项功能。

共有 135 位个人为 Rust 1.16 做出了贡献。感谢你们!