pub
で公開するか制御する
リスト7-5に示したエラーメッセージをnetwork
とnetwork::server
のコードを、
src/network/mod.rsとsrc/network/server.rsファイルにそれぞれ移動することで解決しました。
その時点でcargo build
はプロジェクトをビルドできましたが、
client::connect
とnetwork::connect
とnetwork::server::connect
関数が、
使用されていないという警告メッセージが出ていました:
warning: function is never used: `connect`
--> src/client.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`
--> src/network/mod.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
warning: function is never used: `connect`
--> src/network/server.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
では、何故このような警告を受けているのでしょうか?結局のところ、必ずしも自分のプロジェクト内ではなく、
利用者に利用されることを想定した関数を含むライブラリを構成しているので、
これらのconnect
関数が使用されていかないということは、問題になるはずはありません。
これらの関数を生成することの要点は、自分ではなく、他のプロジェクトで使用することにあるのです。
このプログラムがこのような警告を引き起こす理由を理解するために、外部からcommunicator
ライブラリを呼び出して、
他のプロジェクトからこれを使用してみましょう。そうするには、以下のようなコードを含むsrc/main.rsを作成して、
ライブラリクレートと同じディレクトリにバイナリクレートを作成します。
ファイル名: src/main.rs
extern crate communicator;
fn main() {
communicator::client::connect();
}
extern crate
コマンドを使用して、communicator
ライブラリクレートをスコープに導入しています。
パッケージには2つのクレートが含まれるようになりました。Cargoは、src/main.rsをバイナリクレートのルートファイルとして扱い、
これはルートファイルがsrc/lib.rsになる既存のライブラリクレートとは区別されます。このパターンは、
実行形式プロジェクトで非常に一般的です: ほとんどの機能はライブラリクレートにあり、バイナリクレートはそれを使用するわけです。
結果として、他のプログラムもまたこのライブラリクレートを使用でき、良い責任の分離になるわけです。
communicator
ライブラリの外部のクレートが検索するという観点から言えば、これまでに作ってきたモジュールは全て、
communicator
というクレートと同じ名前を持つモジュール内にあります。クレートのトップ階層のモジュールをルートモジュールと呼びます。
プロジェクトのサブモジュール内で外部クレートを使用しているとしても、extern crate
はルートモジュール(つまり、src/main.rs、
またはsrc/lib.rs)に書くべきということにも、注目してください。それから、
サブモジュールで外部クレートの要素をトップ階層のモジュールかのように参照できるわけです。
現状、バイナリクレートは、client
モジュールからライブラリのconnect
関数を呼び出しているだけです。
ところが、cargo build
を呼び出すと、警告の後にエラーが発生します:
error[E0603]: module `client` is private
(エラー: `client`モジュールは非公開です)
--> src/main.rs:4:5
|
4 | communicator::client::connect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ああ!このエラーは、client
モジュールが非公開であることを教えてくれ、それが警告の肝だったわけです。
Rustの文脈において、公開とか非公開という概念にぶち当たったのは、これが初めてでもあります。
全コードの初期状態は、非公開です: 誰も他の人はコードを使用できないわけです。プログラム内で非公開の関数を使用していなければ、
自分のプログラムだけがその関数を使用することを許可された唯一のコードなので、コンパイラは関数が未使用と警告してくるのです。
client::connect
のような関数を公開にすると指定した後は、バイナリクレートからその関数への呼び出しが許可されるだけでなく、
関数が未使用であるという警告も消え去るわけです。関数を公開にすれば、コンパイラは、
関数が自分のプログラム外のコードからも使用されることがあると知ります。コンパイラは、
関数が「使用されている」という架空の外部使用の可能性を考慮してくれます。それ故に、関数が公開とマークされれば、
コンパイラはそれが自分のプログラムで使用されるべきという要求をなくし、その関数が未使用という警告も止めるのです。
関数を公開にする
コンパイラに何かを公開すると指示するには、定義の先頭にpub
キーワードを追記します。
今は、client::connect
が未使用であるとする警告とバイナリークレートのモジュール`client`が非公開である
エラーの解消に努めます。
src/lib.rsを弄って、client
モジュールを公開にしてください。そう、こんな感じに:
ファイル名: src/lib.rs
pub mod client;
mod network;
pub
キーワードは、mod
の直前に配置されています。再度ビルドしてみましょう:
error[E0603]: function `connect` is private
--> src/main.rs:4:5
|
4 | communicator::client::connect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
やった!違うエラーになりました!そうです、別のエラーメッセージは、祝杯を上げる理由になるのです。
新エラーは、関数`connect`は非公開です
と示しているので、src/client.rsを編集して、
client::connect
も公開にしましょう:
ファイル名: src/client.rs
# #![allow(unused_variables)] #fn main() { pub fn connect() { } #}
さて、再び、cargo build
を走らせてください:
warning: function is never used: `connect`
--> src/network/mod.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`
--> src/network/server.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
コードのコンパイルが通り、client:connect
が使用されていないという警告はなくなりました!
コード未使用警告が必ずしも、コード内の要素を公開にしなければならないことを示唆しているわけではありません: これらの関数を公開APIの一部にしたくなかったら、未使用コード警告がもう必要なく、安全に削除できるコードに注意を向けてくれている可能性もあります。 また未使用コード警告は、ライブラリ内でこの関数を呼び出している箇所全てを誤って削除した場合に、 バグに目を向けさせてくれている可能性もあります。
しかし今回は、本当に他の2つの関数もクレートの公開APIにしたいので、これもpub
とマークして残りの警告を除去しましょう。
src/network/mod.rsを変更して以下のようにしてください:
ファイル名: src/network/mod.rs
pub fn connect() {
}
mod server;
そして、コードをコンパイルします:
warning: function is never used: `connect`
--> src/network/mod.rs:1:1
|
1 | / pub fn connect() {
2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
warning: function is never used: `connect`
--> src/network/server.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
んんー、nework::connect
はpub
に設定されたにもかかわらず、まだ未使用関数警告が出ます。
その理由は、関数はモジュール内で公開になったものの、関数が存在するnetwork
モジュールは公開ではないからです。
今回は、ライブラリの内部から外に向けて作業をした一方、client::connect
では、外から内へ作業をしていました。
src/lib.rsを変えてnetwork
も公開にする必要があります。以下のように:
ファイル名: src/lib.rs
pub mod client;
pub mod network;
これでコンパイルすれば、あの警告はなくなります:
warning: function is never used: `connect`
--> src/network/server.rs:1:1
|
1 | / fn connect() {
2 | | }
| |_^
|
= note: #[warn(dead_code)] on by default
残る警告は1つなので、自分で解消してみてください!
プライバシー規則
まとめると、要素の公開性は以下のようなルールになります:
- 要素が公開なら、どの親モジュールを通してもアクセス可能です。
- 要素が非公開なら、直接の親モジュールとその親の子モジュールのみアクセスできます。
プライバシー例
もうちょっと鍛錬を得るために、もういくつかプライバシー例を見てみましょう。新しいライブラリプロジェクトを作成し、 リスト7-6のコードを新規プロジェクトのsrc/lib.rsに入力してください。
ファイル名: src/lib.rs
mod outermost {
pub fn middle_function() {}
fn middle_secret_function() {}
mod inside {
pub fn inner_function() {}
fn secret_function() {}
}
}
fn try_me() {
outermost::middle_function();
outermost::middle_secret_function();
outermost::inside::inner_function();
outermost::inside::secret_function();
}
このコードをコンパイルする前に、try_me
関数のどの行がエラーになるか当ててみてください。
それからコンパイルを試して、合ってたかどうか確かめ、エラーの議論を求めて読み進めてください!
エラーを確かめる
try_me
関数は、プロジェクトのルートモジュールに存在しています。outermost
という名前のモジュールは非公開ですが、
プライバシー規則の2番目にある通り、try_me
のように、outermost
は現在(ルート)のモジュールなので、
try_me
関数は、outermost
モジュールにアクセスすることを許可されるのです。
middle_function
は公開なので、outermost::middle_function
という呼び出しも動作し、
try_me
はmiddle_function
にその親モジュールのoutermost
を通してアクセスしています。
このモジュールは、アクセス可能と既に決定しました。
outermost::middle_secret_function
の呼び出しは、コンパイルエラーになるでしょう。
middle_secret_function
は非公開なので、2番目の規則が適用されます。ルートモジュールは、
middle_secret_function
の現在のモジュール(outermost
がそうです)でも、
middle_secret_function
の現在のモジュールの子供でもないのです。
inside
という名前のモジュールは非公開で子モジュールを持たないので、現在のモジュールであるoutermost
からのみアクセスできます。
つまり、try_me
関数は、outermost::inside::inner_function
もoutermost::inside::secret_function
も呼び出すことを許されないのです。
エラーを修正する
エラーを修正しようとする過程でできるコード変更案は、以下の通りです。各々試してみる前に、 エラーを解消できるか当ててみてください。それからコンパイルして正しかったか間違っていたか確かめ、 プライバシー規則を使用して理由を理解してください。もっと実験を企てて試してみるのもご自由に!
inside
モジュールが公開だったらどうだろうか?outermost
が公開で、inside
が非公開ならどうだろうか?inner_function
の本体で::outermost::middle_secret_function()
を呼び出したらどうだろうか? (頭の二つのコロンは、ルートモジュールから初めてモジュールを参照したいということを意味します)
今度は、use
キーワードで要素をスコープに導入する話をしましょう。