use キーワードでパスをスコープに導入する
関数を呼び出すたびにパスをすべて書き出さなければならないのは、不便で
繰り返しが多いと感じるかもしれません。リスト 7-7 では、add_to_waitlist
関数への絶対パスと相対パスのどちらを選んだとしても、add_to_waitlist
を呼び出すたびに front_of_house と hosting も指定しなければなりま
せんでした。幸い、この手順を単純化する方法があります。use
キーワードを使って一度パスへのショートカットを作成すれば、そのスコープ
の他の場所では短い名前を使えます。
リスト 7-11 では、crate::front_of_house::hosting モジュールを
eat_at_restaurant 関数のスコープに導入しているので、
hosting::add_to_waitlist とだけ指定すれば、eat_at_restaurant で
add_to_waitlist 関数を呼び出せます。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
スコープ内に use とパスを追加することは、ファイルシステムで
シンボリックリンクを作成することに似ています。クレートルートに
use crate::front_of_house::hosting を追加すると、あたかも hosting
モジュールがクレートルートで定義されていたかのように、そのスコープ内で
hosting は有効な名前になります。use でスコープに導入されたパスも、
他のパスと同じように可視性のチェックを受けます。
use は、その use が現れる特定のスコープに対してのみショートカットを
作成することに注意してください。リスト 7-12 では、eat_at_restaurant
関数を customer という新しい子モジュールに移動しています。この
子モジュールは use 文とは異なるスコープになるため、関数本体は
コンパイルできません。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
コンパイラエラーは、このショートカットが
customer モジュール内ではもはや適用されないことを示しています。
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of unresolved module or unlinked crate `hosting`
|
= help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
さらに、その use がそのスコープ内でもはや使われていないという警告も
出ていることに注目してください。これを修正するには、customer
モジュールの中にも use を移動するか、子モジュール
customer の中で super::hosting を使って親モジュール内の
ショートカットを参照します。
慣用的な use パスを作成する
リスト 7-11 では、なぜ use crate::front_of_house::hosting と指定してから hosting::add_to_waitlist を
eat_at_restaurant の中で呼び出しているのか、同じ結果を得るために
リスト 7-13 のように use のパスを add_to_waitlist 関数まで指定
しないのはなぜか、と疑問に思ったかもしれません。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
リスト 7-11 とリスト 7-13 はどちらも同じことを実現しますが、リスト
7-11 のほうが関数を use でスコープに導入する慣用的な方法です。use
で関数の親モジュールをスコープに導入すると、関数を呼び出すときに
親モジュールを指定する必要があります。関数呼び出し時に親モジュールを
指定すると、その関数がローカルで定義されているわけではないことが明確に
なる一方で、完全なパスの繰り返しは最小限に抑えられます。リスト 7-13 の
コードでは、add_to_waitlist がどこで定義されているのかが不明確です。
一方で、構造体、列挙型、その他の項目を use で導入する場合は、
完全なパスを指定するのが慣用的です。リスト 7-14 は、標準ライブラリの
HashMap 構造体をバイナリクレートのスコープに導入する慣用的な方法
を示しています。
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
この慣習に強い理由があるわけではありません。単にそうした慣例が 生まれ、人々がこの書き方の Rust コードを読み書きすることに慣れた だけです。
この慣習の例外は、use 文で同じ名前を持つ 2 つの項目を
スコープに導入する場合です。Rust ではそれが許されないためです。リスト 7-15
は、同じ名前を持ちながら親モジュールが異なる 2 つの Result 型を
スコープに導入する方法と、それらを参照する方法を示しています。
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
見てのとおり、親モジュールを使うことで 2 つの Result 型を区別できます。
これに対して、use std::fmt::Result と use std::io::Result を指定すると、
同じスコープに 2 つの Result 型が存在することになり、Result を
使ったときに Rust はどちらを意味しているのか判断できません。
as キーワードで新しい名前を与える
同じ名前の 2 つの型を
use で同じスコープに導入する問題には、別の解決策があります。パスの後に as と新しい
ローカル名、つまり エイリアス を指定して、その型に別名を与えることが
できます。リスト 7-16 は、2 つの Result 型のうち一方の名前を as を使って変更することで、
リスト 7-15 のコードを別の形で書く方法を示しています。
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
2 番目の use 文では、std::io::Result 型に対して新しい名前 IoResult を
選んでおり、これなら同じくスコープに導入した std::fmt
の Result と衝突しません。リスト 7-15 とリスト 7-16 は
どちらも慣用的とみなされるので、どちらを選ぶかはあなた次第です。
pub use で名前を再エクスポートする
use キーワードで名前をスコープに導入すると、その名前は導入先の
スコープに対してプライベートになります。そのスコープの外のコードから
も、その名前があたかもそのスコープで定義されているかのように参照
できるようにするには、pub と use を組み合わせます。この手法は、
項目をスコープに導入すると同時に、その項目を他のコードも自分の
スコープに導入できるようにしているため、再エクスポート と呼ばれます。
リスト 7-17 は、ルートモジュール内の use を
pub use に変更したリスト 7-11 のコードを示しています。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
この変更前は、外部コードは add_to_waitlist
関数を
restaurant::front_of_house::hosting::add_to_waitlist() というパスで呼び出す必要があり、
そのためには front_of_house モジュールも pub としてマーク
されていなければなりませんでした。この pub use によって hosting モジュールがルートモジュールから再エクスポートされたので、外部コード
は代わりに restaurant::hosting::add_to_waitlist() というパスを使えます。
再エクスポートは、コードの内部構造が、そのコードを呼び出すプログラマがそのドメインをどう捉えるかと異なる場合に役立ちます。たとえば、このレストランの比喩では、レストランを運営する人たちは「front of house」と「back of house」という観点で考えます。しかし、レストランを訪れる客は、おそらくそのような言い方でレストランの各部分を捉えないでしょう。pub use を使うと、コードはある構造で書きつつ、外部には別の構造を公開できます。こうすることで、ライブラリを開発するプログラマにとっても、そのライブラリを呼び出すプログラマにとっても、よく整理されたライブラリになります。pub use の別の例と、それがクレートのドキュメントにどう影響するかについては、第 14 章の 「便利な公開 API をエクスポートする」 で見ていきます。
外部パッケージを使う
第 2 章では、乱数を取得するために rand という外部パッケージを使う推測ゲームのプロジェクトを作成しました。プロジェクトで rand を使うために、Cargo.toml に次の行を追加しました。
rand = "0.8.5"
Cargo.toml に依存関係として rand を追加すると、Cargo は crates.io から rand パッケージとその依存関係をダウンロードし、rand を私たちのプロジェクトで利用できるようにします。
次に、rand の定義を私たちのパッケージのスコープに導入するために、クレート名である rand から始まる use 行を追加し、スコープに導入したい要素を列挙しました。思い出してください。第 2 章の 「乱数を生成する」 では、Rng トレイトをスコープに導入し、rand::thread_rng 関数を呼び出しました。
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Rust コミュニティのメンバーは、crates.io で多くのパッケージを公開しており、そのどれを自分のパッケージに取り込む場合でも、手順は同じです。つまり、それらを自分のパッケージの Cargo.toml ファイルに列挙し、use を使ってそれらのクレートから要素をスコープに導入します。
標準の std ライブラリも、私たちのパッケージの外部にあるクレートであることに注意してください。標準ライブラリは Rust 言語と一緒に配布されるため、std を含めるために Cargo.toml を変更する必要はありません。しかし、そこから要素を私たちのパッケージのスコープに導入するには、use で参照する必要があります。たとえば、HashMap の場合は次の行を使います。
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
これは、標準ライブラリのクレート名である std から始まる絶対パスです。
use リストを整理するためにネストしたパスを使う
同じクレートや同じモジュールで定義された複数の要素を使う場合、それぞれの要素を別々の行に書くと、ファイル内でかなりの縦方向のスペースを使ってしまうことがあります。たとえば、リスト 2-4 の推測ゲームにあった次の 2 つの use 文は、std から要素をスコープに導入しています。
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
その代わりに、ネストしたパスを使って、同じ要素を 1 行でスコープに導入できます。これは、パスの共通部分を指定し、その後に 2 つのコロンを書き、さらに異なる部分の一覧を波かっこで囲むことで行います。リスト 7-18 に示すとおりです。
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
より大きなプログラムでは、同じクレートやモジュールから多くの要素をネストしたパスでスコープに導入することで、必要な use 文の数を大幅に減らせます。
ネストしたパスは、パスのどのレベルでも使えます。これは、サブパスを共有する 2 つの use 文をまとめるときに便利です。たとえば、リスト 7-19 には 2 つの use 文があります。1 つは std::io をスコープに導入し、もう 1 つは std::io::Write をスコープに導入しています。
use std::io;
use std::io::Write;
この 2 つのパスの共通部分は std::io であり、それが 1 つ目の完全なパスです。この 2 つのパスを 1 つの use 文にまとめるには、リスト 7-20 に示すように、ネストしたパスの中で self を使えます。
use std::io::{self, Write};
この行は、std::io と std::io::Write をスコープに導入します。
グロブ演算子で要素をインポートする
あるパスで定義されている すべての 公開要素をスコープに導入したい場合は、そのパスの後ろに * グロブ演算子を付けて指定できます。
#![allow(unused)]
fn main() {
use std::collections::*;
}
この use 文は、std::collections で定義されているすべての公開要素を現在のスコープに導入します。グロブ演算子を使うときは注意してください。Glob を使うと、どの名前がスコープに入っているのか、またプログラム内で使っている名前がどこで定義されたのかが分かりにくくなります。さらに、依存関係側の定義が変わると、インポートされる内容も変わります。そのため、たとえば依存関係が、同じスコープ内にあるあなた自身の定義と同名の定義を追加した場合、依存関係をアップグレードしたときにコンパイラエラーにつながる可能性があります。
グロブ演算子は、テスト時にテスト対象のすべてを tests モジュールに導入するためによく使われます。これについては、第 11 章の 「テストの書き方」 で説明します。グロブ演算子は、prelude パターンの一部として使われることもあります。そのパターンについて詳しくは、標準ライブラリのドキュメント を参照してください。