路径和模块系统变更
概述
use
声明中的路径现在与其他路径的工作方式相同。- 以
::
开头的路径现在必须后跟外部 crate。 pub(in path)
可见性修饰符中的路径现在必须以crate
、self
或super
开头。
动机
模块系统通常是 Rust 新手最难掌握的内容之一。当然,每个人都有自己需要时间掌握的东西,但它对许多人来说如此令人困惑的根本原因是:虽然有简单且一致的规则定义了模块系统,但它们的后果可能会让人感到不一致、反直觉和神秘。
因此,Rust 的 2018 版本引入了一些新的模块系统功能,但它们最终简化了模块系统,使其更清楚地了解发生了什么。
这是一个简短的总结
- 在 99% 的情况下不再需要
extern crate
。 crate
关键字引用当前 crate。- 路径可以以 crate 名称开头,即使在子模块中也是如此。
- 以
::
开头的路径必须引用外部 crate。 foo.rs
和foo/
子目录可以共存;将子模块放置在子目录中时不再需要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;
。alloc
:alloc
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.rs
或 foo/mod.rs
中。如果它有自己的子模块,它必须是 foo/mod.rs
。因此,foo
的 bar
子模块将位于 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
路径
与 Rust 2015 相比,Rust 2018 简化并统一了路径处理。在 Rust 2015 中,路径在 use
声明中的工作方式与在其他位置不同。特别是,use
声明中的路径将始终从 crate 根目录开始,而其他代码中的路径则隐式从当前作用域开始。这些差异在顶级模块中没有任何影响,这意味着在处理足够大的具有子模块的项目之前,一切似乎都很简单。
在 Rust 2018 中,use
声明和其他代码中的路径以相同的方式工作,无论是在顶级模块还是在任何子模块中。您可以使用来自当前作用域的相对路径,以外部 crate 名称开头的路径,或以 ::
、crate
、super
或 self
开头的路径。
看起来像这样的代码
// 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) => { ... }
}
}
}
这使得在项目中移动代码变得容易,并避免向多模块项目引入额外的复杂性。