路径和模块系统变更
摘要
use
声明中的路径现在与其他路径的工作方式相同。以
::
开头的路径现在必须跟随一个外部 crate 名称。pub(in path)
可见性修饰符中的路径现在必须以crate
、self
或super
开头。
动机
模块系统对于 Rust 新手来说通常是最难掌握的部分之一。当然,每个人都有需要时间才能掌握的东西,但模块系统之所以让很多人感到困惑,有一个根本原因:尽管定义模块系统的规则简单且一致,但其结果可能令人感觉不一致、违反直觉且难以理解。
因此,Rust 2018 版本引入了一些新的模块系统特性,但这些特性最终 简化 了模块系统,使其运作方式更加清晰。
简要总结如下
extern crate
在 99% 的情况下不再需要。crate
关键字指向当前 crate。路径可以以 crate 名称开头,即使在子模块内也可以。
以
::
开头的路径必须引用一个外部 crate。一个
foo.rs
文件和foo/
子目录可以共存;将子模块放在子目录中时,不再需要mod.rs
。use
声明中的路径与其他路径的工作方式相同。
这样说来,这些规则可能看起来是随意的新规则,但总体而言,心智模型(mental model)现在已大大简化。继续阅读以了解更多详情!
更多详情
让我们逐一讨论每个新特性。
不再需要 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” crate。这些是随 Rust 本身一起分发的 crate。
通常,这些只在非常特殊的情况下才需要。从 1.41 版本开始,rustc
接受 --extern=CRATE_NAME
标志,它以类似于 extern crate
的方式自动添加给定的 crate 名称。构建工具可以使用这个特性将 sysroot crate 注入到 crate 的 prelude 中。Cargo 没有一种通用的方式来表达这一点,但它会为 proc_macro
crate 使用此功能。
需要显式导入 sysroot crate 的一些示例包括:
std
:通常这不是必需的,因为除非 crate 被标记为#![no_std]
,否则std
会被自动导入。core
:通常这不是必需的,因为除非 crate 被标记为#![no_core]
,否则core
会被自动导入。例如,标准库本身使用的一些内部 crate 需要这样做。proc_macro
:从 1.42 版本开始,如果这是一个 proc-macro crate,Cargo 会自动导入它。如果你想支持旧版本,或者使用其他未向rustc
传递适当的--extern
标志的构建工具,则需要extern crate proc_macro;
。alloc
:alloc
crate 中的项通常通过std
crate 中的转导出(re-exports)访问。如果你正在处理一个支持分配的no_std
crate,则可能需要显式导入alloc
。test
:这只在 nightly channel 上可用,并且通常只用于不稳定的基准测试(benchmark)支持。
宏
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 中的任何位置,crate::foo::bar
都将始终引用模块 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 中都是可见的(in scope),包括子模块。
// 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,请使用 ::名称
的形式。
不再需要 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) => { ... }
}
}
}
这使得在项目中移动代码变得容易,并避免给多模块项目引入额外的复杂性。