useキーワードでパスをスコープに持ち込む
関数を呼び出すためにパスを略さずに書かなくてはならないのは、繰り返しも多くて不便に感じられるでしょう。
リスト7-7においては、絶対パスを使うか相対パスを使うかにかかわらず、add_to_waitlist関数を呼ぼうと思うたびにfront_of_houseとhostingも指定しないといけませんでした。
ありがたいことに、この手続きを簡単化する方法があります:
一度useキーワードを使ってショートカットを作成すれば、そのスコープ内であればどこでも、より短い名前を使用できます。
リスト7-11では、crate::front_of_house::hostingモジュールをeat_at_restaurant関数のスコープに持ち込むことで、eat_at_restaurantにおいて、hosting::add_to_waitlistと指定するだけでadd_to_waitlist関数を呼び出せるようにしています。
ファイル名: src/lib.rs
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();
}
リスト7-11: use でモジュールをスコープに持ち込む
useとパスをスコープに追加することは、ファイルシステムにおいてシンボリックリンクを張ることに似ています。
use crate::front_of_house::hostingをクレートルートに追加することで、hostingはこのスコープで有効な名前となり、まるでhostingはクレートルートで定義されていたかのようになります。
スコープにuseで持ち込まれたパスも、他のパスと同じようにプライバシーがチェックされます。
useは、useが出現した特定のスコープでのみ使えるショートカットを作成することに注意してください。
リスト7-12はeat_at_restaurant関数を新しい子モジュールcustomerの中に移動していますが、このモジュールはuse文とは異なるスコープなので、この関数本体はコンパイルできないでしょう:
ファイル名: src/lib.rs
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();
}
}
リスト7-12: use文はそれが属するスコープ内にのみ適用される
コンパイルエラーはcustomerモジュール内ではショートカットが適用されないことを示しています:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
(エラー: 名前解決に失敗しました: 宣言されていないクレートまたはモジュール`hosting`の使用)
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
| (宣言されていないクレートまたはモジュール`hosting`の使用)
|
help: consider importing this module through its public re-export
(ヘルプ: 公開再エクスポートからこのモジュールをインポートすることを検討してください)
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
(警告: 未使用のインポート: `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がそのスコープ内で使用されていないという警告も出ていることに気づくでしょう!
この問題を修正するには、useもcustomerモジュールの中に移動するか、customer子モジュールの中でsuper::hostingによって親モジュールにあるショートカットを参照してください。
慣例に従ったuseパスを作る
リスト7-11を見て、なぜuse crate::front_of_house::hostingと書いてeat_at_restaurant内でhosting::add_to_waitlistと呼び出したのか不思議に思っているかもしれません。リスト7-13のように、useでadd_to_waitlistまでのパスをすべて指定しても同じ結果が得られるのに、と。
ファイル名: src/lib.rs
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-13: add_to_waitlist 関数をuse でスコープに持ち込む。このやりかたは慣例的ではない
リスト7-11も7-13もおなじ仕事をしてくれますが、関数をスコープにuseで持ち込む場合、リスト7-11のほうが慣例的なやり方です。
関数の親モジュールをuseで持ち込むということは、関数を呼び出す際、毎回親モジュールを指定しなければならないということです。
関数を呼び出すときに親モジュールを指定することで、フルパスを繰り返して書くことを抑えつつ、関数がローカルで定義されていないことを明らかにできます。
リスト7-13のコードではどこでadd_to_waitlistが定義されたのかが不明瞭です。
一方で、構造体やenumその他の要素をuseで持ち込むときは、フルパスを書くのが慣例的です。
リスト7-14は標準ライブラリのHashMap構造体をバイナリクレートのスコープに持ち込む慣例的なやり方を示しています。
ファイル名: src/main.rs
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
リスト7-14: HashMapを慣例的なやり方でスコープに持ち込む
こちらの慣例の背後には、はっきりとした理由はありません。自然に発生した慣習であり、みんなRustのコードをこのやり方で読み書きするのに慣れてしまったというだけです。
同じ名前の2つの要素をuseでスコープに持ち込むのはRustでは許されないので、そのときこの慣例は例外的に不可能です。
リスト7-15は、同じ名前を持つけれど異なる親モジュールを持つ2つのResult型をスコープに持ち込み、それらを参照するやり方を示しています。
ファイル名: src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
// (略)
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
// (略)
Ok(())
}
リスト7-15: 同じ名前を持つ2つの型を同じスコープに持ち込むには親モジュールを使わないといけない。
このように、親モジュールを使うことで2つのResult型を区別できます。
もしuse std::fmt::Result と use std::io::Resultと書いていたとしたら、2つのResult型が同じスコープに存在することになり、私達がResultを使ったときにどちらのことを意味しているのかRustはわからなくなってしまいます。
新しい名前をasキーワードで与える
同じ名前の2つの型をuseを使って同じスコープに持ち込むという問題には、もう一つ解決策があります。パスの後に、asと型の新しいローカル名、即ちエイリアスを指定すればよいのです。
リスト7-16は、リスト7-15のコードを、2つのResult型のうち一つをasを使ってリネームするという別のやり方で書いたものを表しています。
ファイル名: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
リスト7-16: 型がスコープに持ち込まれた時、asキーワードを使ってその名前を変えている
2つめのuse文では、std::io::Resultに、IoResultという新たな名前を選んでやります。std::fmtのResultもスコープに持ち込んでいますが、この名前はこれとは衝突しません。
リスト7-15もリスト7-16も慣例的とみなされているので、どちらを使っても構いませんよ!
pub useを使って名前を再公開する
useキーワードで名前をスコープに持ちこんだ時、新しいスコープで使用できるその名前は非公開です。
私達のコードを呼び出すコードが、まるでその名前が私達のコードのスコープで定義されていたかのように参照できるようにするためには、pubとuseを組み合わせればいいです。
このテクニックは、要素を自分たちのスコープに持ち込むだけでなく、他の人がその要素をその人のスコープに持ち込むことも可能にすることから、再公開 (re-exporting) と呼ばれています。
リスト7-17はリスト7-11のコードのルートモジュールでのuseをpub useに変更したものを示しています。
ファイル名: src/lib.rs
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();
}
リスト7-17: pub useで、新たなスコープのコードがその名前を使えるようにする
この変更を行う前の状態では、外部のコードはパスrestaurant::front_of_house::hosting::add_to_waitlist()を使用してadd_to_wishlist関数を呼ばなくてはなりませんでした。
このpub useによってルートモジュールからhostingモジュールを再公開された今、外部のコードはパスrestaurant::hosting::add_to_waitlist()を使用できるようになりました。
再公開は、あなたのコードの内部構造と、あなたのコードを呼び出すプログラマーたちのその領域に関しての見方が異なるときに有用です。
例えば、レストランの比喩では、レストランを経営している人は「接客部門 (front of house)」と「後方部門 (back of house)」のことについて考えるでしょう。
しかし、レストランを訪れるお客さんは、そのような観点からレストランの部門について考えることはありません。
pub useを使うことで、ある構造でコードを書きつつも、別の構造で公開するということが可能になります。
こうすることで、私達のライブラリを、ライブラリを開発するプログラマにとっても、ライブラリを呼び出すプログラマにとっても、よく整理されたものとすることができます。
第14章の「pub useで便利な公開APIをエクスポートする」の節で、pub useの別の例と、それがクレートのドキュメンテーションにどう影響するかを見ることにしましょう。
外部のパッケージを使う
2章で、乱数を得るためにrandという外部パッケージを使って、数当てゲームをプログラムしました。
randを私達のプロジェクトで使うために、次の行を Cargo.toml に書き加えましたね:
ファイル名: Cargo.toml
rand = "0.8.5"
randを依存 (dependency) として Cargo.toml に追加すると、randパッケージとそのすべての依存をcrates.ioからダウンロードして、私達のプロジェクトでrandが使えるようにするようCargoに命令します。
そして、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}"); //秘密の数字は次の通り: {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言語に同梱されているので、 Cargo.toml を stdを含むように変更する必要はありません。
しかし、その要素をそこから私達のパッケージのスコープに持ち込むためには、useを使って参照する必要はあります。
例えば、HashMapには次の行を使います。
#![allow(unused)] fn main() { use std::collections::HashMap; }
これは標準ライブラリクレートの名前stdから始まる絶対パスです。
巨大なuseのリストをネストしたパスを使って整理する
同じクレートか同じモジュールで定義された複数の要素を使おうとする時、それぞれの要素を一行一行並べると、縦に大量のスペースを取ってしまいます。
例えば、リスト2-4の数当てゲームで使った次の2つのuse文がstdからスコープへ要素を持ち込みました。
ファイル名: src/main.rs
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行でスコープに持ち込めます。 これをするには、リスト7-18に示されるように、パスの共通部分を書き、2つのコロンを続け、そこで波括弧で互いに異なる部分のパスのリストを囲みます。
ファイル名: src/main.rs
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!"),
}
}
リスト7-18: 同じプレフィックスをもつ複数の要素をスコープに持ち込むためにネストしたパスを指定する
大きなプログラムにおいては、同じクレートやモジュールからのたくさんの要素をネストしたパスで持ち込むようにすれば、独立したuse文の数を大きく減らすことができます!
ネストしたパスはパスのどの階層においても使うことができます。これはサブパスを共有する2つのuse文を合体させるときに有用です。
例えば、リスト7-19は2つのuse文を示しています:1つはstd::ioをスコープに持ち込み、もう一つはstd::io::Writeをスコープに持ち込んでいます。
ファイル名: src/lib.rs
use std::io;
use std::io::Write;
リスト7-19: 片方がもう片方のサブパスである2つのuse文
これらの2つのパスの共通部分はstd::ioであり、そしてこれは最初のパスにほかなりません。これらの2つのパスを1つのuse文へと合体させるには、リスト7-20に示されるように、ネストしたパスにselfを使いましょう。
ファイル名: src/lib.rs
use std::io::{self, Write};
リスト7-20: リスト7-19のパスを一つの use 文に合体させる
この行は std::io とstd::io::Write をスコープに持ち込みます。
glob演算子
パスにおいて定義されているすべての公開要素をスコープに持ち込みたいときは、glob演算子 * をそのパスの後ろに続けて書きましょう:
#![allow(unused)] fn main() { use std::collections::*; }
このuse文はstd::collectionsのすべての公開要素を現在のスコープに持ち込みます。
glob演算子を使う際にはご注意を!
globをすると、どの名前がスコープ内にあり、プログラムで使われている名前がどこで定義されたのか分かりづらくなります。
glob演算子はしばしば、テストの際、テストされるあらゆるものをtestsモジュールに持ち込むために使われます。これについては11章テストの書き方の節で話します。
glob演算子はプレリュードパターンの一部としても使われることがあります:そのようなパターンについて、より詳しくは標準ライブラリのドキュメントをご覧ください。