use
キーワードでパスをスコープに持ち込む
これまで関数呼び出しのために書いてきたパスは、長く、繰り返しも多くて不便なものでした。
例えば、Listing 7-7 においては、絶対パスを使うか相対パスを使うかにかかわらず、add_to_waitlist
関数を呼ぼうと思うたびにfront_of_house
とhosting
も指定しないといけませんでした。
ありがたいことに、この手続きを簡単化する方法があります。
use
キーワードを使うことで、パスを一度スコープに持ち込んでしまえば、それ以降はパス内の要素がローカルにあるかのように呼び出すことができるのです。
Listing 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(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); } fn main() {}
use
とパスをスコープに追加することは、ファイルシステムにおいてシンボリックリンクを張ることに似ています。
use crate::front_of_house::hosting
をクレートルートに追加することで、hosting
はこのスコープで有効な名前となり、まるでhosting
はクレートルートで定義されていたかのようになります。
スコープにuse
で持ち込まれたパスも、他のパスと同じようにプライバシーがチェックされます。
use
と相対パスで要素をスコープに持ち込むこともできます。
Listing 7-12 はListing 7-11 と同じふるまいを得るためにどう相対パスを書けば良いかを示しています。
ファイル名: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use self::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); } fn main() {}
慣例に従ったuse
パスを作る
Listing 7-11 を見て、なぜuse crate::front_of_house::hosting
と書いてeat_at_restaurant
内でhosting::add_to_waitlist
と呼び出したのか不思議に思っているかもしれません。Listing 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(); add_to_waitlist(); add_to_waitlist(); } fn main() {}
Listing 7-11 も 7-13 もおなじ仕事をしてくれますが、関数をスコープにuse
で持ち込む場合、Listing 7-11 のほうが慣例的なやり方です。
関数の親モジュールをuse
で持ち込むことで、関数を呼び出す際、毎回親モジュールを指定しなければならないようにすれば、フルパスを繰り返して書くことを抑えつつ、関数がローカルで定義されていないことを明らかにできます。
Listing 7-13 のコードではどこでadd_to_waitlist
が定義されたのかが不明瞭です。
一方で、構造体やenumその他の要素をuse
で持ち込むときは、フルパスを書くのが慣例的です。
Listing 7-14 は標準ライブラリのHashMap
構造体をバイナリクレートのスコープに持ち込む慣例的なやり方を示しています。
ファイル名: src/main.rs
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
こちらの慣例の背後には、はっきりとした理由はありません。自然に発生した慣習であり、みんなRustのコードをこのやり方で読み書きするのに慣れてしまったというだけです。
同じ名前の2つの要素をuse
でスコープに持ち込むのはRustでは許されないので、そのときこの慣例は例外的に不可能です。
Listing 7-15は、同じ名前を持つけれど異なる親モジュールを持つ2つのResult
型をスコープに持ち込み、それらを参照するやり方を示しています。
ファイル名: src/lib.rs
#![allow(unused)] fn main() { 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
と型の新しいローカル名、即ちエイリアスを指定すればよいのです。
Listing 7-16 は、Listing 7-15 のコードを、2つのResult
型のうち一つをas
を使ってリネームするという別のやり方で書いたものを表しています。
ファイル名: src/lib.rs
#![allow(unused)] fn main() { 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
もスコープに持ち込んでいますが、この名前はこれとは衝突しません。
Listing 7-15もListing 7-16も慣例的とみなされているので、どちらを使っても構いませんよ!
pub use
を使って名前を再公開する
use
キーワードで名前をスコープに持ちこんだ時、新しいスコープで使用できるその名前は非公開です。
私達のコードを呼び出すコードが、まるでその名前が私達のコードのスコープで定義されていたかのように参照できるようにするためには、pub
とuse
を組み合わせればいいです。
このテクニックは、要素を自分たちのスコープに持ち込むだけでなく、他の人がその要素をその人のスコープに持ち込むことも可能にすることから、再公開 (re-exporting) と呼ばれています。
Listing 7-17 は Listing 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(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); } fn main() {}
pub use
を使うことで、外部のコードがhosting::add_to_waitlist
を使ってadd_to_waitlist
関数を呼び出せるようになりました。
pub use
を使っていなければ、eat_at_restaurant
関数はhosting::add_to_waitlist
を自らのスコープ内で使えるものの、外部のコードはこの新しいパスを利用することはできないでしょう。
再公開は、あなたのコードの内部構造と、あなたのコードを呼び出すプログラマーたちのその領域に関しての見方が異なるときに有用です。
例えば、レストランの比喩では、レストランを経営している人は「接客部門 (front of house)」と「後方部門 (back of house)」のことについて考えるでしょう。
しかし、レストランを訪れるお客さんは、そのような観点からレストランの部門について考えることはありません。
pub use
を使うことで、ある構造でコードを書きつつも、別の構造で公開するということが可能になります。
こうすることで、私達のライブラリを、ライブラリを開発するプログラマにとっても、ライブラリを呼び出すプログラマにとっても、よく整理されたものとすることができます。
外部のパッケージを使う
2章で、乱数を得るためにrand
という外部パッケージを使って、数当てゲームをプログラムしました。
rand
を私達のプロジェクトで使うために、次の行を Cargo.toml に書き加えましたね:
ファイル名: Cargo.toml
rand = "0.8.3"
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..101);
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言語に同梱されているので、 Cargo.toml を std
を含むように変更する必要はありません。
しかし、その要素をそこから私達のパッケージのスコープに持ち込むためには、use
を使って参照する必要はあります。
例えば、HashMap
には次の行を使います。
#![allow(unused)] fn main() { use std::collections::HashMap; }
これは標準ライブラリクレートの名前std
から始まる絶対パスです。
巨大なuse
のリストをネストしたパスを使って整理する
同じクレートか同じモジュールで定義された複数の要素を使おうとする時、それぞれの要素を一行一行並べると、縦に大量のスペースを取ってしまいます。
例えば、Listing 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, 101);
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行でスコープに持ち込めます。 これをするには、Listing 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, 101);
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
文を合体させるときに有用です。
例えば、Listing 7-19 は2つのuse
文を示しています:1つはstd::io
をスコープに持ち込み、もう一つはstd::io::Write
をスコープに持ち込んでいます。
ファイル名: src/lib.rs
#![allow(unused)] fn main() { use std::io; use std::io::Write; }
これらの2つのパスの共通部分はstd::io
であり、そしてこれは最初のパスにほかなりません。これらの2つのパスを1つのuse
文へと合体させるには、Listing 7-20 に示されるように、ネストしたパスにself
を使いましょう。
ファイル名: src/lib.rs
#![allow(unused)] fn main() { use std::io::{self, Write}; }
この行は std::io
とstd::io::Write
をスコープに持ち込みます。
glob演算子
パスにおいて定義されているすべての公開要素をスコープに持ち込みたいときは、glob演算子 *
をそのパスの後ろに続けて書きましょう:
#![allow(unused)] fn main() { use std::collections::*; }
このuse
文はstd::collections
のすべての公開要素を現在のスコープに持ち込みます。
glob演算子を使う際にはご注意を!
globをすると、どの名前がスコープ内にあり、プログラムで使われている名前がどこで定義されたのか分かりづらくなります。
glob演算子はしばしば、テストの際、テストされるあらゆるものをtests
モジュールに持ち込むために使われます。これについては11章テストの書き方の節で話します。
glob演算子はプレリュードパターンの一部としても使われることがあります:そのようなパターンについて、より詳しくは標準ライブラリのドキュメントをご覧ください。