パスとモジュールシステムへの変更
概要
use宣言中のパスが、他のパスと同じように扱われるようになりました。::から始まるパスの直後には、常に外部クレートが続くようになりました。pub(in path)のような可視性修飾子[^1]において、パスはcrate,self,superのいずれかで始まらなくてはならなくなりました。
[^1]
pub(in path) 構文は、アイテムを path に指定したモジュール内だけに公開にするための可視性修飾子です。
例えば、pub(in crate::outer_mod) のように書くと crate::outer_mod モジュール内だけからアクセスできるようになります。("public in crate::outer_mod" という意味です。)
詳細は The Rust Reference (英語) の説明もご参照ください。
動機
Rust を習って間もない人にとって、モジュールシステムは最も難しいものであることが多いです。 もちろん、習得に時間がかかるものは誰にでもあります。 しかし、モジュールシステムが多くの人を混乱させる根本的原因は、モジュールシステムの定義そのものは単純で首尾一貫しているにも関わらず、その帰結が一貫性のない、非直感的な、不思議なものに感じられてしまうことにあります。
ですので、Rust の 2018 エディションにおけるモジュールシステムの新機能は、モジュールシステムを単純化し、仕組みをよりわかりやくするために導入されました。
簡単にまとめると、
extern crateは九割九分の場面で必要なくなりました。crateキーワードは、自身のクレートを指します。- サブモジュール内であっても、パスをクレート名から始めることができます。
::から始まるパスは、常に外部クレートを指します。foo.rsとfoo/サブディレクトリは共存できます。サブディレクトリにサブモジュールを置く場合でも、mod.rsは必要なくなりました。use宣言におけるパスも、他の場所のパスと同じように書けます。
こうして並べられると、一見これらの新しい規則はてんでバラバラですが、これにより、総じて今までよりはるかに「こうすればこう動くだろう」という直感が通じるようになりました。 詳しく説明していきましょう!
詳しく説明
新機能を一つずつ取り上げていきます。
さようなら、extern crate
これは非常に単純な話です。
プロジェクトにクレートをインポートするために extern crate を書く必要はもはやありません。
今まで:
// Rust 2015
extern crate futures;
mod submodule {
use futures::Future;
}
これから:
// Rust 2018
mod submodule {
use futures::Future;
}
今や、プロジェクトに新しくクレートを追加したかったら、Cargo.toml に追記して、それで終わりです。
Cargo を使用していない場合は、rustc に外部クレートの場所を --extern フラグを渡しているでしょうが、これを変える必要はありません。
一つ注意ですが、
cargo fixは今の所この変更を自動では行いません。 将来、自動で変更がなされるようになるかもしれません。
例外
このルールには一つだけ例外があります。それは、"sysroot" のクレートです。 これらのクレートは、Rust に同梱されています。
通常は、これらが必要なのは非常に特殊な状況です。
1.41 以降、rustc は --extern=CRATE_NAME フラグを受け付けるようになりました。
このフラグを指定すると、extern crate で指定するのと同じように、与えられたクレート名が自動的に追加されます。
ビルドツールは、このフラグを用いてクレートのプレリュードに sysroot のクレートを注入できます。
Cargo にはこれを表現する汎用的な方法はありませんが、proc_macro のクレートではこれが使用されます。
例えば、以下のような場合には明示的に sysroot のクレートをインポートする必要があります:
std: 通常は不要です。クレートに#![no_std]が指定されていない限り、stdは自動的にインポートされるからです。core: 通常は不要です。クレートに#![no_core]が指定されていない限り、coreは自動的にインポートされるからです。 例えば、標準ライブラリに使用されている一部の内部クレートではこれが必要です。proc_macro: 1.42 以降、proc-macro クレートではこれは自動的にインポートされます。 それより古いリリースをサポートしたい場合か、Cargo 以外のビルドツールを使っていてそれがrustcに適切な--externフラグを渡さない場合は、extern crate proc_macro;と書く必要があります。alloc:allocクレート内のアイテムは、通常はstdを通して公開されたものが使用されます。 アロケーションをサポートするno_stdなクレートにおいては、明示的にallocをインポートすることが必要になります。test: これは nightly チャンネルでのみ使用可能で、まだ安定化されていないベンチマークのために主に使われます。
マクロ
extern crate のもう一つの使い道は、マクロのインポートでしたが、これも必要なくなりました。
マクロは、他のアイテムと同様、use でインポートできます。
たとえば、以下の extern crate は:
#[macro_use]
extern crate bar;
fn main() {
baz!();
}
こんな感じに変えることができます:
use bar::baz;
fn main() {
baz!();
}
クレートの名前変更
今までは as を使ってクレートを違う名前でインポートしていたとします:
extern crate futures as f;
use f::Future;
その場合、 extern crate の行を消すだけではうまくいきません。こう書く必要があります:
use futures as f;
use self::f::Future;
f を使っているすべてのモジュールに対して、同様の変更が必要です。
crate キーワードは自身のクレートを指す
use 宣言や他のコードでは、crate:: プレフィクスを使って自身のクレートのルートを指すことができます。
たとえば、crate::foo::bar と書けば、それがどこに書かれていようと、foo モジュール内の bar という名前を指します。
:: というプレフィクスは、かつてはクレートのルートまたは外部クレートのいずれかを指していましたが、今は常に外部クレートを指します。
例えば、::foo::bar と書くと、これは常に foo というクレートの bar という名前を指します。
外部クレートのパス
かつては、use によるインポートなしで外部クレートを使用するには、:: から始まるパスを書かなくてはなりませんでした。
// 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`
// 一方、サブモジュールでは `use` でインポートしない限り :: から始めないといけない
let x = ::chrono::Utc::now();
}
}
今は、外部クレートの名前はサブモジュールを含むクレート全体でスコープに含まれます。
// 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();
}
}
さようなら、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` のいずれかに探す
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 2018 では、Rust 2015 に比べてパスの扱いが単純化・統一されています。
Rust 2015 では、use 宣言におけるパスは他の場所と異なった挙動を示しました。
特に、use 宣言におけるパスは常にクレートのルートを基準にしたのに対し、プログラム中の他の場所でのパスは暗黙に現在のスコープが基準になっていました。
トップレベルモジュールではこの2つに違いはなかったので、プロジェクトがサブモジュールを導入するほど大きくない限りはすべては単純に見えました。
Rust 2018 では、トップレベルモジュールかサブモジュールかに関わらず、use 宣言でのパスと他のプログラム中のパスは同じように使用できます。
現在のスコープからの相対パスも、外部クレート名から始まるパスも、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) => { ... }
}
}
}
これにより、コードを他の場所に移動することが簡単になり、マルチモジュールなプロジェクトがより複雑になるのを防止できます。
もし、例えば外部モジュールとローカルのモジュールが同名であるなど、パスが曖昧な場合は、エラーになります。
その場合、他と衝突している名前のうち一方を変更するか、明示的にパスの曖昧性をなくす必要があります。
パスの曖昧性をなくすには、::name と書いて外部クレート名であることを明示するか、self::name と書いてローカルのモジュールやアイテムであることを明示すればよいです。