use キーワードを使ってパスをスコープに持ち込む

Beginner

This tutorial is from open-source community. Access the source code

はじめに

「use キーワードを使ってパスをスコープに持ち込む」へようこそ。この実験は、Rust ブックの一部です。LabEx で Rust のスキルを練習することができます。

この実験では、関数やモジュールを呼び出すためのショートカットを作成するために、use キーワードを使ってパスをスコープに持ち込む方法を学びます。

use キーワードを使ってパスをスコープに持ち込む

関数を呼び出すためのパスを書き出す必要があると、不便で繰り返しに感じることがあります。リスト 7-7 では、add_to_waitlist 関数への絶対パスまたは相対パスを選択した場合でも、add_to_waitlist を呼び出したいたびに、front_of_househosting も指定する必要がありました。幸いなことに、このプロセスを簡略化する方法があります。use キーワードを使ってパスのショートカットを一度作成し、その後、スコープ内の他の場所では短い名前を使用することができます。

リスト 7-11 では、crate::front_of_house::hosting モジュールを eat_at_restaurant 関数のスコープに持ち込み、eat_at_restaurantadd_to_waitlist 関数を呼び出すために、hosting::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 に移動します。このとき、customeruse 文とは異なるスコープになるため、関数本体はコンパイルされません。

ファイル名: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 モジュール内ではショートカットがもはや適用されないことを示しています。

error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of undeclared crate or module `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

use がそのスコープ内でもはや使用されていないことを示す警告もあることに注意してください。この問題を解決するには、customer モジュール内にも use を移動するか、子モジュール customer 内で親モジュールのショートカットを super::hosting で参照します。

慣用的な use パスの作成

リスト 7-11 では、なぜ use crate::front_of_house::hosting を指定してから、eat_at_restauranthosting::add_to_waitlist を呼び出したのか、リスト 7-13 のように、同じ結果を得るために add_to_waitlist 関数までの use パスを指定しなかったのか疑問に思ったかもしれません。

ファイル名: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: use を使って add_to_waitlist 関数をスコープに持ち込む、非慣用的な方法

リスト 7-11 とリスト 7-13 のどちらも同じタスクを達成していますが、リスト 7-11 が use を使って関数をスコープに持ち込む慣用的な方法です。use を使って関数の親モジュールをスコープに持ち込むことは、関数を呼び出す際に親モジュールを指定する必要があることを意味します。関数を呼び出す際に親モジュールを指定することで、関数がローカルで定義されていないことが明確になり、同時に完全なパスの繰り返しを最小限に抑えます。リスト 7-13 のコードは、add_to_waitlist がどこに定義されているか不明です。

一方、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--
}

fn function2() -> io::Result<()> {
    --snip--
}

リスト 7-15: 同じ名前の 2 つの型を同じスコープに持ち込むには、それらの親モジュールを使用する必要があります。

ご覧の通り、親モジュールを使用することで 2 つの Result 型が区別されます。代わりに use std::fmt::Resultuse std::io::Result を指定した場合、同じスコープ内に 2 つの Result 型があり、Result を使用した際に Rust はどちらを意味するのかわかりません。

as キーワードを使って新しい名前を提供する

同じ名前の 2 つの型を use で同じスコープに持ち込む問題に対する別の解決策があります。パスの後に、型に対する新しいローカル名、つまり エイリアス を指定することができます。リスト 7-16 は、2 つの Result 型のうちの 1 つを as を使ってリネームすることで、リスト 7-15 のコードを書き換える別の方法を示しています。

ファイル名:src/lib.rs

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    --snip--
}

fn function2() -> IoResult<()> {
    --snip--
}

リスト 7-16: as キーワードを使って型をスコープに持ち込む際にリネームする

2 番目の use 文では、std::io::Result 型に対して新しい名前 IoResult を選びました。これは、同じスコープに持ち込んだ std::fmt からの Result とは競合しません。リスト 7-15 とリスト 7-16 は慣用的な書き方と考えられているので、選択はあなた次第です!

pub use による名前の再エクスポート

use キーワードを使って名前をスコープに持ち込むと、新しいスコープで利用可能な名前は非公開になります。コードを呼び出すコードが、その名前がそのコードのスコープ内に定義されたかのように参照できるようにするには、pubuse を組み合わせることができます。この技法は 再エクスポート と呼ばれます。なぜなら、私たちは項目をスコープに持ち込むだけでなく、他の人がその項目を自分たちのスコープに持ち込めるようにしているからです。

リスト 7-17 は、ルートモジュールの usepub use に変更したリスト 7-11 のコードを示しています。

ファイル名: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_waitlist 関数を呼び出す必要がありました。この pub use によりルートモジュールから hosting モジュールが再エクスポートされたので、外部コードは代わりに restaurant::hosting::add_to_waitlist() というパスを使うことができます。

再エクスポートは、コードの内部構造が、コードを呼び出すプログラマーがドメインを考える方法と異なる場合に役立ちます。たとえば、このレストランの例では、レストランを運営する人は「フロント オブ ハウス」と「バック オブ ハウス」を考えます。しかし、レストランに来るお客様はおそらくそのような用語でレストランの部分を考えません。pub use を使うことで、私たちは 1 つの構造でコードを書くことができますが、異なる構造を公開することができます。これにより、ライブラリを作成するプログラマーと、ライブラリを呼び出すプログラマーの両方にとって、ライブラリをうまく整理することができます。「pub use による便利なパブリック API のエクスポート」では、pub use の別の例と、それがクレートのドキュメントにどのような影響を与えるかを見ていきます。

外部パッケージの使用

第 2 章では、ランダムな数値を取得するために rand と呼ばれる外部パッケージを使用した予想ゲーム プロジェクトを作成しました。プロジェクトで rand を使用するには、Cargo.toml にこの行を追加しました。

ファイル名:Cargo.toml

rand = "0.8.5"

Cargo.tomlrand を依存関係として追加することで、Cargo に対して rand パッケージとその依存関係を https://crates.io からダウンロードし、rand をプロジェクトで利用できるようにするよう指示します。

次に、rand の定義をパッケージのスコープに持ち込むために、クレート名である rand から始まる use 行を追加し、スコープに持ち込みたい項目を列挙しました。「乱数の生成」では、Rng トレイトをスコープに持ち込み、rand::thread_rng 関数を呼び出しました。

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}

Rust コミュニティのメンバーは、https://crates.io に多くのパッケージを公開しています。それらのいずれかをパッケージに取り込むには、同じ手順が必要です。パッケージの Cargo.toml ファイルにそれらを列挙し、use を使ってそれらのクレートからの項目をスコープに持ち込みます。

標準の std ライブラリも、私たちのパッケージに対して外部のクレートです。標準ライブラリは Rust 言語と一緒に提供されるため、Cargo.toml を変更して std を含める必要はありません。ただし、そこからの項目をパッケージのスコープに持ち込むには、use を使って参照する必要があります。たとえば、HashMap の場合、この行を使用します。

use std::collections::HashMap;

これは、標準ライブラリ クレートの名前である std から始まる絶対パスです。

ネストされたパスを使って大きな use リストを整理する

同じクレートまたは同じモジュールに定義された複数の項目を使用する場合、それぞれの項目を個別の行に列挙すると、ファイル内で多くの垂直スペースを占めることになります。たとえば、リスト 2-4 の予想ゲームで使用したこれら 2 つの use 文は、std からの項目をスコープに持ち込みます。

ファイル名:src/main.rs

--snip--
use std::cmp::Ordering;
use std::io;
--snip--

代わりに、ネストされたパスを使って、同じ項目を 1 行でスコープに持ち込むことができます。これは、パスの共通部分を指定し、その後に 2 つのコロンを続け、その後に異なるパスの部分のリストを囲む波括弧を使うことで行います。これはリスト 7-18 に示されています。

ファイル名:src/main.rs

--snip--
use std::{cmp::Ordering, io};
--snip--

リスト 7-18: 同じ接頭辞を持つ複数の項目をスコープに持ち込むためのネストされたパスを指定する

より大きなプログラムでは、同じクレートまたはモジュールから多数の項目をネストされたパスを使ってスコープに持ち込むことで、必要な個別の use 文の数を大幅に減らすことができます!

パスのどのレベルでもネストされたパスを使用できます。これは、サブパスを共有する 2 つの use 文を結合する際に便利です。たとえば、リスト 7-19 は 2 つの use 文を示しています。1 つは std::io をスコープに持ち込み、もう 1 つは std::io::Write をスコープに持ち込みます。

ファイル名:src/lib.rs

use std::io;
use std::io::Write;

リスト 7-19: 1 つがもう 1 つのサブパスである 2 つの use

これら 2 つのパスの共通部分は std::io であり、これが最初の完全なパスです。これら 2 つのパスを 1 つの use 文にマージするには、ネストされたパスで self を使用することができます。これはリスト 7-20 に示されています。

ファイル名:src/lib.rs

use std::io::{self, Write};

リスト 7-20: リスト 7-19 のパスを 1 つの use 文に結合する

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

グロブ演算子

パスに定義された すべての パブリック項目をスコープに持ち込みたい場合は、そのパスの後に * グロブ演算子を指定できます。

use std::collections::*;

この use 文は、std::collections に定義されたすべてのパブリック項目を現在のスコープに持ち込みます。グロブ演算子を使用する際は注意してください!グロブを使用すると、スコープ内にある名前や、プログラムで使用される名前がどこで定義されているかを判断するのが難しくなる場合があります。

グロブ演算子は、テスト時にテスト対象のすべてのものを tests モジュールに持ち込むためによく使用されます。「テストの書き方」でそれについて説明します。グロブ演算子は、時にはプレリュード パターンの一部としても使用されます。そのパターンに関する詳細は、標準ライブラリのドキュメントを参照してください。

まとめ

おめでとうございます!「use キーワードを使ってパスをスコープに持ち込む」実験を完了しました。さらに実験を行って技術力を向上させるために、LabEx でさらに多くの実験を行ってみてください。