路径和模块系统变更

Minimum Rust version: 1.31

概述

  • use 声明中的路径现在与其他路径的工作方式相同。
  • :: 开头的路径现在必须后跟外部 crate。
  • pub(in path) 可见性修饰符中的路径现在必须以 crateselfsuper 开头。

动机

模块系统通常是 Rust 新手最难掌握的内容之一。当然,每个人都有自己需要时间掌握的东西,但它对许多人来说如此令人困惑的根本原因是:虽然有简单且一致的规则定义了模块系统,但它们的后果可能会让人感到不一致、反直觉和神秘。

因此,Rust 的 2018 版本引入了一些新的模块系统功能,但它们最终简化了模块系统,使其更清楚地了解发生了什么。

这是一个简短的总结

  • 在 99% 的情况下不再需要 extern crate
  • crate 关键字引用当前 crate。
  • 路径可以以 crate 名称开头,即使在子模块中也是如此。
  • :: 开头的路径必须引用外部 crate。
  • foo.rsfoo/ 子目录可以共存;将子模块放置在子目录中时不再需要 mod.rs
  • use 声明中的路径与其他路径的工作方式相同。

这样说似乎是任意的新规则,但整体的心理模型现在已大大简化。请继续阅读以了解更多详情!

更多详情

让我们依次讨论每个新功能。

不再需要 extern crate

这很简单:您不再需要编写 extern crate 将 crate 导入到您的项目中。之前

// Rust 2015

extern crate futures;

mod submodule {
    use futures::Future;
}

之后

// Rust 2018

mod submodule {
    use futures::Future;
}

现在,要向您的项目添加新的 crate,您可以将其添加到您的 Cargo.toml 中,然后就没有第二步了。如果您不使用 Cargo,您已经必须传递 --extern 标志以向 rustc 提供外部 crate 的位置,因此您只需继续执行您在那里所做的事情即可。

一个例外

此规则有一个例外,那就是“sysroot” crates。这些是与 Rust 本身一起分发的 crates。

通常,这些仅在非常特殊的情况下才需要。从 1.41 版本开始,rustc 接受 --extern=CRATE_NAME 标志,该标志以类似于 extern crate 的方式自动添加给定的 crate 名称。构建工具可以使用此标志将 sysroot crates 注入到 crate 的 prelude 中。Cargo 没有表达这一点的通用方法,尽管它将其用于 proc_macro crates。

一些需要显式导入 sysroot crates 的示例是

  • std:通常这不是必需的,因为会自动导入 std,除非该 crate 标记有 #![no_std]
  • core:通常这不是必需的,因为会自动导入 core,除非该 crate 标记有 #![no_core]。例如,标准库本身使用的一些内部 crate 需要这样做。
  • proc_macro:如果它是从 1.42 版本开始的 proc-macro crate,则 Cargo 会自动导入它。如果您想支持旧版本,或者如果使用不向 rustc 传递适当的 --extern 标志的其他构建工具,则需要 extern crate proc_macro;
  • allocalloc crate 中的项通常通过 std crate 中的重新导出进行访问。如果您正在使用支持分配的 no_std crate,那么您可能需要显式导入 alloc
  • test:这仅在 nightly channel 上可用,并且通常仅用于不稳定的基准测试支持。

extern crate 的另一种用途是导入宏;现在不再需要了。宏可以使用 use 像其他项一样导入。例如,以下使用 extern crate 的示例

#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

可以更改为类似以下内容

use bar::baz;

fn main() {
    baz!();
}

重命名 crates

如果您一直使用 as 来重命名您的 crate,如下所示

extern crate futures as f;

use f::Future;

那么单独删除 extern crate 行将不起作用。您需要这样做

use futures as f;

use self::f::Future;

此更改需要在任何使用 f 的模块中发生。

crate 关键字引用当前 crate

use 声明和其他代码中,您可以使用 crate:: 前缀引用当前 crate 的根目录。例如,crate::foo::bar 将始终引用同一 crate 中任何其他位置的模块 foo 内的名称 bar

前缀 :: 以前引用 crate 根目录或外部 crate;它现在明确地引用外部 crate。例如,::foo::bar 始终引用外部 crate foo 内的名称 bar

外部 crate 路径

以前,在没有 use 导入的情况下在模块中使用外部 crate 需要路径上有一个前导 ::

// Rust 2015

extern crate chrono;

fn foo() {
    // this works in the crate root
    let x = chrono::Utc::now();
}

mod submodule {
    fn function() {
        // but in a submodule it requires a leading :: if not imported with `use`
        let x = ::chrono::Utc::now();
    }
}

现在,外部 crate 名称在整个 crate(包括子模块)中都有效。

// Rust 2018

fn foo() {
    // this works in the crate root
    let x = chrono::Utc::now();
}

mod submodule {
    fn function() {
        // crates may be referenced directly, even in submodules
        let x = chrono::Utc::now();
    }
}

如果您有一个与外部 crate 同名的本地模块或项,则以该名称开头的路径将用于引用本地模块或项。要显式引用外部 crate,请使用 ::name 形式。

不再需要 mod.rs

在 Rust 2015 中,如果您有一个子模块

// This `mod` declaration looks for the `foo` module in
// `foo.rs` or `foo/mod.rs`.
mod foo;

它可以位于 foo.rsfoo/mod.rs 中。如果它有自己的子模块,它必须foo/mod.rs。因此,foobar 子模块将位于 foo/bar.rs

在 Rust 2018 中,删除了具有子模块的模块必须命名为 mod.rs 的限制。foo.rs 可以是 foo.rs,子模块仍然是 foo/bar.rs。这消除了特殊名称,并且如果您在编辑器中打开了一堆文件,您可以清楚地看到它们的名字,而不是有一堆名为 mod.rs 的标签。

Rust 2015 Rust 2018
.
├── lib.rs
└── foo/
    ├── mod.rs
    └── bar.rs
.
├── lib.rs
├── foo.rs
└── foo/
    └── bar.rs

use 路径

Minimum Rust version: 1.32

与 Rust 2015 相比,Rust 2018 简化并统一了路径处理。在 Rust 2015 中,路径在 use 声明中的工作方式与在其他位置不同。特别是,use 声明中的路径将始终从 crate 根目录开始,而其他代码中的路径则隐式从当前作用域开始。这些差异在顶级模块中没有任何影响,这意味着在处理足够大的具有子模块的项目之前,一切似乎都很简单。

在 Rust 2018 中,use 声明和其他代码中的路径以相同的方式工作,无论是在顶级模块还是在任何子模块中。您可以使用来自当前作用域的相对路径,以外部 crate 名称开头的路径,或以 ::cratesuperself 开头的路径。

看起来像这样的代码

// Rust 2015

extern crate futures;

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

在 Rust 2018 中的外观完全相同,只是您可以删除 extern crate

// Rust 2018

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

相同的代码在子模块中也可以完全未修改地工作

// Rust 2018

mod submodule {
    use futures::Future;

    mod foo {
        pub struct Bar;
    }

    use foo::Bar;

    fn my_poll() -> futures::Poll { ... }

    enum SomeEnum {
        V1(usize),
        V2(String),
    }

    fn func() {
        let five = std::sync::Arc::new(5);
        use SomeEnum::*;
        match ... {
            V1(i) => { ... }
            V2(s) => { ... }
        }
    }
}

这使得在项目中移动代码变得容易,并避免向多模块项目引入额外的复杂性。