路径和模块系统变更
总结
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 的位置,因此您只需继续执行您在那里所做的操作即可。
这里需要注意一点:
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;
。alloc
:alloc
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.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) => { ... }
}
}
}
这使得在项目中移动代码变得容易,并避免了向多模块项目引入额外的复杂性。