路径和模块系统变更

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 的位置,因此您只需继续执行您在那里所做的操作即可。

这里需要注意一点:cargo fix 目前不会自动执行此更改。我们将来可能会让它为您执行此操作。

例外情况

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

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

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

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

extern crate 的另一个用途是导入宏;现在不再需要这样做了。宏可以使用 use 导入,就像任何其他项目一样。例如,以下 extern crate 的使用

#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

可以更改为如下所示

use bar::baz;

fn main() {
    baz!();
}

重命名 crate

如果您一直在使用 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 将始终引用模块 foo 内的名称 bar,无论在同一个 crate 中的任何其他位置。

前缀 :: 以前指的是 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) => { ... }
        }
    }
}

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