宣布 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 稳定版中的内容

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

过程宏

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

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

并使用 serde_jsonPet 转换为 JSON 格式,因为 serde_derive 在过程宏中定义了 SerializeDeserialize

Rust 1.30 通过添加定义另外两种高级宏的能力来扩展此功能,即“属性式过程宏”和“函数式过程宏”。

属性式宏类似于自定义派生宏,但它们不仅为 #[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 {

这类似于派生宏的签名:我们获取括号内的标记,并返回我们要生成的代码。

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`

模块系统改进

模块系统长期以来一直是新 Rustaceans 的痛点;它的许多规则在实践中感觉很别扭。这些更改是我们为使模块系统感觉更直接而采取的第一步。

除了前面提到的宏更改之外,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 根目录开始的旧行为仍然适用。但是,在一次性切换到新样式后,这些规则将统一应用于所有地方的路径,并且在移动代码时,您需要调整导入的次数将大大减少。

原始标识符

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

// 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] 属性自行实现恐慌。这意味着您现在可以构建不使用标准库的应用程序,而不仅仅是库。

其他内容

最后,您现在可以使用在宏中匹配可见性关键字(如 pub,使用 vis 说明符。此外,#[rustfmt::skip] 之类的“工具属性”现在已稳定。但是,#[allow(clippy::something)] 之类的工具lint 尚未稳定。

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

库稳定化

此版本稳定了一些新的 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。没有你们,我们不可能做到。感谢!