宣布 Rust 1.30

2018 年 10 月 25 日 · Rust 核心团队

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

如果您已经通过 rustup 安装了之前的 Rust 版本,那么获取 Rust 1.30.0 非常简单,只需运行

$ rustup update stable

如果您尚未安装,可以从我们网站的相应页面获取 rustup,并在 GitHub 上查阅 1.30.0 的详细发行说明

1.30.0 stable 中有什么

Rust 1.30 是一个令人兴奋的版本,包含许多特性。周一,预计会发布另一篇博客文章,请您试用 Rust 1.31 的 beta 版本;Rust 1.31 将是“Rust 2018”的第一个版本。有关该概念的更多信息,请参阅我们之前的文章 “什么是 Rust 2018”

过程宏

早在 Rust 1.15 中,我们就宣布了定义“自定义派生(custom derives)”的能力。例如,使用 serde_derive,您可以

#[derive(Serialize, Deserialize, Debug)]
struct Pet {
    name: String,
}

并使用 serde_jsonPet 转换为 JSON 或从 JSON 转换,因为 serde_derive 在一个过程宏中定义了 SerializeDeserialize

Rust 1.30 在此基础上进行了扩展,增加了定义另外两种高级宏的能力:“类属性过程宏(attribute-like procedural macros)”和“类函数过程宏(function-like procedural macros)”。

类属性宏类似于自定义派生宏,但它们不仅为 #[derive] 属性生成代码,还允许您创建自己的新自定义属性。它们也更灵活:派生(derive)只适用于结构体和枚举,但属性可以用于其他地方,例如函数。以下是使用类属性宏的示例,在使用 Web 应用框架时您可能会看到类似这样的代码

#[route(GET, "/")]
fn index() {

这个 #[route] 属性将由框架本身定义,作为一个过程宏。它的签名会是这样

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

这里,我们有两个输入 TokenStreams:第一个用于属性本身的内容,即 GET, "/" 等内容。第二个是属性所附加的内容体,在本例中是 fn index() {} 以及函数体的其余部分。

类函数宏定义了看起来像函数调用的宏。例如,gnome-class crate 有一个过程宏,用于在 Rust 中定义 GObject

gobject_gen!(
    class MyClass: GObject {
        foo: Cell<i32>,
        bar: RefCell<String>,
    }

    impl MyClass {
        virtual fn my_virtual_method(&self, x: i32) {
            // ... do something with x ...
        }
    }
)

这看起来像一个将一堆代码作为参数的函数。这个宏的定义方式如下

#[proc_macro]
pub fn gobject_gen(input: TokenStream) -> TokenStream {

这类似于派生宏的签名:我们获取括号内的 token,然后返回我们想要生成的代码。

use 和宏

现在,您可以使用 use 关键字将宏引入作用域。例如,以前使用 serde-jsonjson 宏时,您需要这样写

#[macro_use]
extern crate serde_json;

let john = json!({
    "name": "John Doe",
    "age": 43,
    "phones": [
        "+44 1234567",
        "+44 2345678"
    ]
});

但现在,您可以这样写

extern crate serde_json;

use serde_json::json;

let john = json!({
    "name": "John Doe",
    "age": 43,
    "phones": [
        "+44 1234567",
        "+44 2345678"
    ]
});

这使得宏与其他项的使用方式更加一致,并且不再需要 macro_use 注解。

最后,proc_macro crate 被稳定化,它提供了编写这类宏所需的 API。它还显著改进了错误相关的 API,像 synquote 这样的 crate 已经在使用它们。例如,之前

#[derive(Serialize)]
struct Demo {
    ok: String,
    bad: std::thread::Thread,
}

会产生这样的错误

error[E0277]: the trait bound `std::thread::Thread: _IMPL_SERIALIZE_FOR_Demo::_serde::Serialize` is not satisfied
 --> src/main.rs:3:10
  |
3 | #[derive(Serialize)]
  |          ^^^^^^^^^ the trait `_IMPL_SERIALIZE_FOR_Demo::_serde::Serialize` is not implemented for `std::thread::Thread`

现在则会产生这样的错误

error[E0277]: the trait bound `std::thread::Thread: serde::Serialize` is not satisfied
 --> src/main.rs:7:5
  |
7 |     bad: std::thread::Thread,
  |     ^^^ the trait `serde::Serialize` is not implemented for `std::thread::Thread`

模块系统改进

模块系统长期以来一直是 Rust 新手的一个痛点;它的一些规则在实践中令人感到别扭。这些改变是我们正在采取的第一步,以使模块系统感觉更直观。

除了前面提到的宏方面的变化之外,use 还有两处变化。首先是 外部 crate 现在位于 prelude 中,也就是说

// old
let json = ::serde_json::from_str("...");

// new
let json = serde_json::from_str("...");

这里的技巧在于,由于 Rust 模块系统的工作方式,‘旧’的写法并非总是必需的

extern crate serde_json;

fn main() {
    // this works just fine; we're in the crate root, so `serde_json` is in
    // scope here
    let json = serde_json::from_str("...");
}

mod foo {
    fn bar() {
        // this doesn't work; we're inside the `foo` namespace, and `serde_json`
        // isn't declared there
        let json = serde_json::from_str("...");

    }

    // one option is to `use` it inside the module
    use serde_json;

    fn baz() {
        // the other option is to use `::serde_json`, so we're using an absolute path
        // rather than a relative one
        let json = ::serde_json::from_str("...");
    }
}

将一个函数移动到子模块并导致部分代码损坏不是一个很好的体验。现在,它会检查路径的第一部分,看它是否是一个 extern crate,如果是,无论您在模块层级中的哪个位置,都会使用它。

最后,use 还支持将以 crate 开头的路径项引入作用域

mod foo {
    pub fn bar() {
        // ...
    }
}

// old
use ::foo::bar;
// or
use foo::bar;

// new
use crate::foo::bar;

路径开头的 crate 关键字表示您希望路径从您的 crate 根目录开始。以前,在 use 之后指定的路径总是从 crate 根目录开始,但直接引用项的路径会从本地路径开始,这意味着路径的行为是不一致的

mod foo {
    pub fn bar() {
        // ...
    }
}

mod baz {
    pub fn qux() {
        // old
        ::foo::bar();
        // does not work, which is different than with `use`:
        // foo::bar();

        // new
        crate::foo::bar();
    }
}

一旦这种风格得到广泛使用,有望使绝对路径更加清晰,并消除开头 :: 的一些不美观之处。

所有这些变化结合起来,使理解路径如何解析变得更加直观。无论您在 use 语句以外的任何地方看到像 a::b::c 这样的路径,您都可以这样问

  • a 是一个 crate 的名称吗?如果是,那么我们在其中寻找 b::c
  • a 是关键字 crate 吗?如果是,那么我们从 crate 根目录寻找 b::c
  • 否则,我们从模块层级中的当前位置寻找 a::b::c

use 路径始终从 crate 根目录开始的旧行为仍然适用。但在一次性切换到新风格后,这些规则将统一适用于所有地方的路径,并且在移动代码时,您调整引入(imports)的次数会大大减少。

原始标识符

现在您可以使用一些新的语法将关键字作为标识符使用

// define a local variable named `for`
let r#for = true;

// define a function named `for`
fn r#for() {
    // ...
}

// call that function
r#for();

目前,这种用法没有太多实际用例,但在您尝试将 Rust 2015 的 crate 与 Rust 2018 的项目一起使用,反之亦然时,它就会有用,因为这两个版本中的关键字集会有所不同;我们将在即将发布的关于 Rust 2018 的博客文章中详细解释。

no_std 应用

早在 Rust 1.6 中,我们就宣布了 用于构建不依赖标准库的项目而稳定化 no_stdlibcore。但是有一个限制:您只能构建库,不能构建应用。

有了 Rust 1.30,您可以使用 #[panic_handler] 属性自行实现 panic。这现在意味着您可以构建不使用标准库的应用,而不仅仅是库。

其他内容

最后,您现在可以使用 vis specifier 在宏中 匹配可见性关键字,例如 pub。此外,像 #[rustfmt::skip] 这样的“工具属性(tool attributes)”现在已稳定。然而,像 #[allow(clippy::something)] 这样的工具 lints 尚未稳定。

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

标准库稳定化

本次发布中稳定化了一些新的 API

  • Ipv4Addr::{BROADCAST, LOCALHOST, UNSPECIFIED}
  • Ipv6Addr::{LOCALHOST, UNSPECIFIED}
  • Iterator::find_map

此外,标准库长期以来一直有像 trim_left 这样的函数,用于去除文本一侧的空白。然而,在考虑从右到左(RTL)的语言时,“右”和“左”的含义会变得令人困惑。因此,我们为这些 API 引入了新的名称

  • trim_left -> trim_start
  • trim_right -> trim_end
  • trim_left_matches -> trim_start_matches
  • trim_right_matches -> trim_end_matches

我们计划在 Rust 1.33 中废弃(当然不是移除)旧名称。

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

Cargo 特性

本次发布中 Cargo 最大的特性是,我们现在 有进度条了!

demo gif

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

1.30.0 的贡献者

许多人共同努力创建了 Rust 1.30。没有你们,我们不可能完成。 感谢!