pubで公開するか制御する

リスト7-5に示したエラーメッセージをnetworknetwork::serverのコードを、 src/network/mod.rssrc/network/server.rsファイルにそれぞれ移動することで解決しました。 その時点でcargo buildはプロジェクトをビルドできましたが、 client::connectnetwork::connectnetwork::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::connectpubに設定されたにもかかわらず、まだ未使用関数警告が出ます。 その理由は、関数はモジュール内で公開になったものの、関数が存在する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();
}

リスト7-6: 公開と非公開関数の例。不正なものもあります

このコードをコンパイルする前に、try_me関数のどの行がエラーになるか当ててみてください。 それからコンパイルを試して、合ってたかどうか確かめ、エラーの議論を求めて読み進めてください!

エラーを確かめる

try_me関数は、プロジェクトのルートモジュールに存在しています。outermostという名前のモジュールは非公開ですが、 プライバシー規則の2番目にある通り、try_meのように、outermostは現在(ルート)のモジュールなので、 try_me関数は、outermostモジュールにアクセスすることを許可されるのです。

middle_functionは公開なので、outermost::middle_functionという呼び出しも動作し、 try_memiddle_functionにその親モジュールのoutermostを通してアクセスしています。 このモジュールは、アクセス可能と既に決定しました。

outermost::middle_secret_functionの呼び出しは、コンパイルエラーになるでしょう。 middle_secret_functionは非公開なので、2番目の規則が適用されます。ルートモジュールは、 middle_secret_functionの現在のモジュール(outermostがそうです)でも、 middle_secret_functionの現在のモジュールの子供でもないのです。

insideという名前のモジュールは非公開で子モジュールを持たないので、現在のモジュールであるoutermostからのみアクセスできます。 つまり、try_me関数は、outermost::inside::inner_functionoutermost::inside::secret_functionも呼び出すことを許されないのです。

エラーを修正する

エラーを修正しようとする過程でできるコード変更案は、以下の通りです。各々試してみる前に、 エラーを解消できるか当ててみてください。それからコンパイルして正しかったか間違っていたか確かめ、 プライバシー規則を使用して理由を理解してください。もっと実験を企てて試してみるのもご自由に!

  • insideモジュールが公開だったらどうだろうか?
  • outermostが公開で、insideが非公開ならどうだろうか?
  • inner_functionの本体で::outermost::middle_secret_function()を呼び出したらどうだろうか? (頭の二つのコロンは、ルートモジュールから初めてモジュールを参照したいということを意味します)

今度は、useキーワードで要素をスコープに導入する話をしましょう。