useキーワードでパスをスコープに持ち込む

これまで関数呼び出しのために書いてきたパスは、長く、繰り返しも多くて不便なものでした。 例えば、Listing 7-7 においては、絶対パスを使うか相対パスを使うかにかかわらず、add_to_waitlist関数を呼ぼうと思うたびにfront_of_househostingも指定しないといけませんでした。 ありがたいことに、この手続きを簡単化する方法があります。 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() {}

Listing 7-11: use でモジュールをスコープに持ち込む

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() {}

Listing 7-12: モジュールをuseと相対パスを使ってスコープに持ち込む

慣例に従ったuseパスを作る

Listing 7-11 を見て、なぜuse crate::front_of_house::hostingと書いてeat_at_restaurant内でhosting::add_to_waitlistと呼び出したのか不思議に思っているかもしれません。Listing 7-13 のように、useadd_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-13: add_to_waitlist 関数をuse でスコープに持ち込む。このやりかたは慣例的ではない

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);
}

Listing 7-14: HashMapを慣例的なやり方でスコープに持ち込む

こちらの慣例の背後には、はっきりとした理由はありません。自然に発生した慣習であり、みんな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(())
}
}

Listing 7-15: 同じ名前を持つ2つの型を同じスコープに持ち込むには親モジュールを使わないといけない。

このように、親モジュールを使うことで2つのResult型を区別できます。 もしuse std::fmt::Resultuse 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(())
}
}

Listing 7-16: 型がスコープに持ち込まれた時、asキーワードを使ってその名前を変えている

2つめのuse文では、std::io::Resultに、IoResultという新たな名前を選んでやります。std::fmtResultもスコープに持ち込んでいますが、この名前はこれとは衝突しません。 Listing 7-15もListing 7-16も慣例的とみなされているので、どちらを使っても構いませんよ!

pub useを使って名前を再公開する

useキーワードで名前をスコープに持ちこんだ時、新しいスコープで使用できるその名前は非公開です。 私達のコードを呼び出すコードが、まるでその名前が私達のコードのスコープで定義されていたかのように参照できるようにするためには、pubuseを組み合わせればいいです。 このテクニックは、要素を自分たちのスコープに持ち込むだけでなく、他の人がその要素をその人のスコープに持ち込むことも可能にすることから、再公開 (re-exporting) と呼ばれています。

Listing 7-17 は Listing 7-11 のコードのルートモジュールでのusepub 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() {}

Listing 7-17: pub useで、新たなスコープのコードがその名前を使えるようにする

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.tomlstdを含むように変更する必要はありません。 しかし、その要素をそこから私達のパッケージのスコープに持ち込むためには、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!"),
    }
}

Listing 7-18: 同じプレフィックスをもつ複数の要素をスコープに持ち込むためにネストしたパスを指定する

大きなプログラムにおいては、同じクレートやモジュールからのたくさんの要素をネストしたパスで持ち込むようにすれば、独立した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;
}

Listing 7-19: 片方がもう片方のサブパスである2つのuse

これらの2つのパスの共通部分はstd::ioであり、そしてこれは最初のパスにほかなりません。これらの2つのパスを1つのuse文へと合体させるには、Listing 7-20 に示されるように、ネストしたパスにselfを使いましょう。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
use std::io::{self, Write};
}

Listing 7-20: Listing 7-19 のパスを一つの use 文に合体させる

この行は std::iostd::io::Write をスコープに持ち込みます。

glob演算子

パスにおいて定義されているすべての公開要素をスコープに持ち込みたいときは、glob演算子 * をそのパスの後ろに続けて書きましょう:


#![allow(unused)]
fn main() {
use std::collections::*;
}

このuse文はstd::collectionsのすべての公開要素を現在のスコープに持ち込みます。 glob演算子を使う際にはご注意を! globをすると、どの名前がスコープ内にあり、プログラムで使われている名前がどこで定義されたのか分かりづらくなります。

glob演算子はしばしば、テストの際、テストされるあらゆるものをtestsモジュールに持ち込むために使われます。これについては11章テストの書き方の節で話します。 glob演算子はプレリュードパターンの一部としても使われることがあります:そのようなパターンについて、より詳しくは標準ライブラリのドキュメントをご覧ください。

関連キーワード:  use, スコープ, パス, Listing, hosting, waitlist, pub, 要素, println, 名前