Rustのモジュールの使い方、気になっていませんか?
プログラムが育ってくると、まるで部屋が散らかるみたいにコードもごちゃごちゃしがちですよね。そんな悩みをスッキリ解決してくれるのが、Rustの「モジュール」という仕組みなんです!
この記事では、Rustのモジュールについて、その基本からコードを別ファイルに分ける方法、他の人にも使ってもらえるように公開する設定まで、初心者の方にも分かりやすいように、たくさんのコード例を使いながら解説していきます。
この記事でわかること
- モジュールって何? その役割が分かる
- `mod` キーワードを使ってモジュールを作る方法が分かる
- `pub` キーワードでモジュールの中身を公開する方法が分かる
- `use` キーワードで他のモジュールを楽に使う方法が分かる
- だんだん長くなるコードを別のファイルにきれいに分ける方法が分かる
Rustのモジュールとは?
さて、「モジュール」とは一体何者なんでしょう? 簡単に言うと、関連する関数や構造体、定数なんかをひとまとめにして、名前を付けて管理するための箱みたいなものです。
プログラムを作り始めの頃は、全部 main.rs ファイルに書いても問題ないかもしれません。でも、機能が増えてコードが長くなってくると…
- どこに何が書いてあるか探すのが大変!
- 似たような名前の関数を作っちゃってエラー!
- ちょっと修正したいだけなのに、他の部分まで影響しそうで怖い!
なんてことになりがちです。まるで、いろんな物がごちゃ混ぜになった引き出しを探るような感じですね。
そこでモジュールの出番! モジュールを使うと、こんな良いことがあります。
- 整理整頓
機能ごとに関連するコードをまとめられるので、どこに何があるか分かりやすくなる。 - 名前の衝突回避
モジュールごとに名前空間(名前が有効な範囲)が区切られるので、違うモジュールで同じ名前の関数などを使っても大丈夫になる。 - 再利用しやすくなる
ある機能がひとまとまりになっているので、他のプロジェクトでも使い回しやすくなる。 - 隠蔽(カプセル化)
モジュールの外に見せたくない部分(内部だけで使う関数など)を隠せるので、安全性が高まる。
プログラムをきれいに保ち、開発しやすくするための、いわば「整理術」の基本がモジュールなんです。
Rustのモジュールの基本的な書き方 (`mod`キーワード)
じゃあ、実際にモジュールはどうやって作るんでしょうか? 使うのは mod
というキーワードです。一番シンプルな形は、こんな風にコードの中に直接書く方法です。
書き方
mod モジュール名 { // ここにモジュールの中身(関数、構造体など)を書く }
例えば、「あいさつ」に関する関数をまとめる `greeting` というモジュールを作ってみましょう。
ソースコード
// greeting モジュールを定義 mod greeting { // モジュールの中に hello 関数を定義 fn hello() { println!("Hello!"); } } fn main() { // 今はまだモジュールの外から hello 関数を呼べない // greeting::hello(); // これはエラーになる println!("main 関数を実行しました。"); }
ソースコードの表示結果 (コンパイルは通るが、helloは呼べない)
main 関数を実行しました。
上の例では、mod greeting { ... }
の中に hello
関数を定義しました。これで `greeting` という名前の箱ができたイメージです。
ただし、このままでは箱の中身(hello
関数)は外から見えない(使えない)状態です。どうすれば使えるようになるかは、次のセクションで見ていきましょう。
モジュール内の要素へのアクセス方法 (パス)
モジュールの中に作った関数や構造体を使うには、「どのモジュールの、どの要素か」を明示する必要があります。これを「パス」を使って指定します。
パスの書き方は、パソコンのフォルダ階層を指定するのに少し似ています。区切り文字には ::
(ダブルコロン) を使います。
主なパスの種類
- 絶対パス
クレート(プロジェクト全体のことだと思ってください)のルートから指定する方法。crate::モジュール名::要素名
のように書く。 - 相対パス
今いる場所からの関係で指定する方法。self::要素名
(同じモジュール内の要素)super::要素名
(親モジュールの要素)
先ほどの `greeting` モジュールの例で、main
関数から hello
関数を使いたい場合、もし `hello` 関数が公開されていれば(公開方法は次のセクションで!)、絶対パスで crate::greeting::hello()
のように書くことになります。
ソースコード (まだエラーになる例)
mod greeting { // 仮に hello が公開されているとする (実際はまだ非公開) // pub fn hello() { ... } fn hello() { // ← まだ pub がないので非公開 println!("Hello from greeting module!"); } } fn main() { // 絶対パスで呼び出そうとしてみる // crate::greeting::hello(); // ← ここでエラー! (非公開のため) println!("main 関数が呼ばれました。"); }
パスの指定自体は crate::greeting::hello
で合っていますが、まだ `hello` 関数が「非公開」設定なので、コンパイルエラーになってしまいます。では、いよいよ公開する方法を見ていきましょう!
モジュール要素の公開と非公開 (`pub`キーワード)
Rustでは、モジュールの中に書いた関数や構造体などは、デフォルト(何もしない状態)ではすべて「非公開(プライベート)」になっています。
これは、モジュールの内部実装を勝手に外部からいじられないようにするための安全装置みたいなものです。
外から使えるようにするには、公開したい要素の前に pub
キーワードを付けます。pub
は「public(公開の)」の略ですね。
書き方
mod モジュール名 { // この関数は公開される pub fn public_function() { println!("公開関数だよ!"); } // この関数は非公開のまま fn private_function() { println!("非公開関数だよ。"); } // 構造体自体を公開 pub struct PublicStruct { // フィールドも公開する場合は pub を付ける pub public_field: i32, // このフィールドは非公開 private_field: i32, } } fn main() { // 公開されている関数はパスで呼び出せる crate::モジュール名::public_function(); // 非公開の関数は呼び出せない (エラーになる) // crate::モジュール名::private_function(); // 公開されている構造体を使える let my_struct = crate::モジュール名::PublicStruct { public_field: 100, // 非公開フィールドは初期化時に指定できない (エラー) // private_field: 200, }; // 公開フィールドにはアクセスできる println!("公開フィールドの値: {}", my_struct.public_field); // 非公開フィールドにはアクセスできない (エラー) // println!("非公開フィールドの値: {}", my_struct.private_field); }
先ほどの `greeting` モジュールの `hello` 関数を公開してみましょう。
ソースコード
mod greeting { // pub を付けて hello 関数を公開! pub fn hello() { println!("Hello from greeting module!"); } } fn main() { // これで呼び出せる! crate::greeting::hello(); }
表示結果
Hello from greeting module!
これで、モジュールの外から `hello` 関数を使えるようになりました!
構造体の場合は、構造体自体を `pub` にしても、中のフィールドが自動で `pub` になるわけではない点に注意しましょう。フィールドごとに公開するかどうか決められます。
`use`キーワードでパスを短縮!Rustのモジュールの使い方を効率化
モジュールが深くなったり、たくさんのモジュールを使ったりすると、毎回 crate::hoge::fuga::piyo()
みたいに長いパスを書くのはちょっと面倒ですよね。
そこで便利なのが use
キーワード! これを使うと、使いたい要素(モジュール、関数、構造体など)を、もっと短い名前で使えるように「ショートカット」を作れるんです。
書き方
// パスの全体を指定して use する use crate::モジュール名::要素名; // モジュール自体を use して、使うときに モジュール名::要素名 とする use crate::モジュール名; // 複数の要素をまとめて use する use crate::モジュール名::{要素1, 要素2}; // 別名を付けて use する (名前が衝突する場合などに) use crate::モジュール名::要素名 as 別名; fn main() { // use した後は短い名前で使える! 要素名(); モジュール名::要素1(); 別名(); }
greeting::hello
を use
してみましょう。
ソースコード
mod greeting { pub fn hello() { println!("Hello!"); } pub fn goodbye() { println!("Goodbye!"); } } // greeting モジュールの hello と goodbye を use する use crate::greeting::{hello, goodbye}; // use crate::greeting::hello; // こう書いてもOK // use crate::greeting::goodbye; // こう書いてもOK fn main() { // use したので、直接関数名で呼び出せる! hello(); goodbye(); // もちろんフルパスでも呼び出せる crate::greeting::hello(); }
表示結果
Hello! Goodbye! Hello!
use
は通常、ファイルの先頭の方(mod
宣言の後など)にまとめて書くことが多いです。これでコードがスッキリして読みやすくなりますね!
Rustのモジュールを別ファイルに分割する方法
さて、モジュールを使ってコードを論理的に分割できるようになりましたが、main.rs
や lib.rs
ファイル自体がどんどん長くなっていくと、やっぱり見通しが悪くなります。
そこで、モジュールを物理的に別のファイルに分けてしまいましょう! これで、ファイル単位でコードを管理できるようになります。
Rustでは、mod モジュール名;
という宣言があると、コンパイラは以下のルールで対応するファイルを探しに行きます。
- 同じディレクトリにある
モジュール名.rs
ファイル - 同じディレクトリにある
モジュール名
という名前のディレクトリの中のmod.rs
ファイル
(最近のRustでは、2番目のパターンで モジュール名/モジュール名.rs
というファイル名も使えるようになっていますが、まずは基本の2パターンを覚えましょう)
例を見てみましょう。
【パターン1】同じ階層の `.rs` ファイル
ファイル構成図 (イメージ)
src/ ├── main.rs └── greeting.rs
src/main.rs
の中身
// greeting.rs をモジュールとして宣言 mod greeting; // greeting モジュールの hello 関数を使う use crate::greeting::hello; fn main() { hello(); }
src/greeting.rs
の中身
// このファイル全体が greeting モジュールになる pub fn hello() { println!("Hello from greeting.rs!"); }
【パターン2】サブディレクトリ内の `mod.rs`
ファイル構成図 (イメージ)
src/ ├── main.rs └── network/ └── mod.rs
src/main.rs
の中身
// network ディレクトリをモジュールとして宣言 mod network; // network モジュールの connect 関数を使う (network/mod.rs で pub されている前提) use crate::network::connect; fn main() { connect(); }
src/network/mod.rs
の中身
// このファイルが network モジュールになる pub fn connect() { println!("Connecting from network/mod.rs!"); }
このように、mod モジュール名;
と書くだけで、Rustが対応するファイルを探してきてくれるんですね。大きなプロジェクトでは、ファイルを分割していくことがコードを管理しやすくするカギになります。
ファイル分割の具体例 (サブディレクトリ)
もう少し複雑な例として、ネットワーク関連の機能をまとめる `network` モジュールを作り、その中にさらに `client` と `server` というサブモジュールを作る場合を見てみましょう。
ファイル構成図 (イメージ)
src/ ├── main.rs └── network/ <-- network モジュール (ディレクトリ) ├── mod.rs <-- network モジュールの本体&サブモジュール宣言 └── client.rs <-- client サブモジュール
src/main.rs
の中身
// network モジュールを宣言 (src/network/mod.rs を探す) mod network; // network モジュールの中の client モジュールの中の connect 関数を使う // network/mod.rs で pub mod client; と pub use client::connect; が必要 use crate::network::connect_as_client; // 別名で公開された関数を使う例 fn main() { println!("--- main.rs ---"); network::ping(); // network モジュール直下の関数 connect_as_client(); // network モジュール経由で client モジュールの関数を使う }
src/network/mod.rs
の中身
// client.rs をサブモジュールとして宣言 pub mod client; // pub を付けて client モジュール自体を公開 // この network モジュール直下の関数 pub fn ping() { println!("network::ping() called"); } // client モジュールの connect 関数を network モジュール直下から使えるように再公開 (別名で) // 外部からは network::connect_as_client() として見える pub use client::connect as connect_as_client; // pub use client::connect; // そのままの名前で再公開する場合
src/network/client.rs
の中身
// このファイルが client モジュールになる pub fn connect() { println!("network::client::connect() called"); }
ソースコードの表示結果
--- main.rs --- network::ping() called network::client::connect() called
このように、network/mod.rs
が親モジュールとして、サブモジュールである client.rs
(pub mod client;
で宣言) をまとめ、さらに外部への見せ方 (pub use ...
) をコントロールする役割を担っています。
階層的にファイルを整理することで、大規模なプロジェクトでもコードの置き場所が明確になりますね。
ファイル分割の具体例 (同階層ファイル)
もっとシンプルな例として、よく使う便利関数などをまとめた `utils` モジュールを `main.rs` と同じ階層に置くパターンを見てみましょう。これは手軽に始められるので便利です。
ファイル構成図 (イメージ)
src/ ├── main.rs └── utils.rs <-- utils モジュール
src/main.rs
の中身
// utils.rs をモジュールとして宣言 mod utils; // utils モジュールの関数を使う use crate::utils::say_hello; fn main() { println!("--- main.rs ---"); say_hello("World"); // use しない場合はこう書く // crate::utils::say_hello("World"); }
src/utils.rs
の中身
// このファイルが utils モジュールになる pub fn say_hello(name: &str) { // & は参照を表す記号 println!("Hello, {} from utils.rs!", name); } // この関数は pub がないので非公開 #[allow(dead_code)] // 使わないことによる警告を抑制 fn internal_helper() { println!("This is an internal helper."); }
ソースコードの表示結果
--- main.rs --- Hello, World from utils.rs!
main.rs
に mod utils;
と書くだけで、自動的に utils.rs
を見つけてモジュールとして認識してくれる手軽さがポイントです。
ちょっとした共通処理をまとめたい時に、まず試してみるのに良い方法かもしれません。
Rustモジュールを使う上での注意点とよくあるエラー
モジュールは便利ですが、最初はいくつか戸惑う点や、よく出会うエラーがあるかもしれません。いくつか代表的なものとその対処法を見てみましょう。
Q1: `unresolved import` や `use of undeclared crate or module` というエラーが出た!
A1: これは、指定したパスが見つからないよ、というエラーです。以下の点を確認しましょう。
- パスのスペルミスはないか (
crate::greeting
をcrate::greting
とか) - ファイル分割している場合、親モジュールでちゃんと
mod モジュール名;
と宣言しているか use
しようとしている要素にpub
が付いているか (非公開だと見つけられない)- 絶対パス (
crate::...
) か相対パス (super::...
,self::...
) か、意図した通りになっているか
Q2: 関数や構造体を使おうとしたら `is private` というエラーが出た!
A2: これは、アクセスしようとした要素が非公開ですよ、というエラーです。使いたい要素(関数、構造体、構造体のフィールドなど)の定義の前に pub
を付け忘れていないか確認しましょう。モジュール自体が pub
でも、中の要素が pub
でなければ外からは使えません。
Q3: ファイル分割したら、サブモジュールの中から親や兄弟のモジュールにある要素を使えない!
A3: サブモジュールから親モジュールの要素にアクセスするには、相対パス super::
を使います。兄弟モジュール(同じ親を持つ別のモジュール)の要素にアクセスするには、一度親に上がってから下がるイメージで super::兄弟モジュール名::要素名
のように書くか、あるいはルートからの絶対パス crate::...
を使います。もちろん、アクセス先の要素が pub
で公開されている必要があります。
ソースコード例 (サブモジュールから親や兄弟)
// 親モジュール mod parent { pub fn parent_func() { println!("Parent func"); } // サブモジュール sibling pub mod sibling { pub fn sibling_func() { println!("Sibling func"); } } // サブモジュール child pub mod child { pub fn child_func() { println!("Child func"); // 親の関数を呼ぶ super::parent_func(); // 兄弟の関数を呼ぶ super::sibling::sibling_func(); } } } fn main() { crate::parent::child::child_func(); }
ソースコードの表示結果
Child func Parent func Sibling func
エラーが出ると最初は焦るかもしれませんが、エラーメッセージは解決のための大きなヒントです。落ち着いてメッセージを読み、どのファイル(パス)のどの部分で問題が起きているのかを確認する癖をつけると良いでしょう。
【まとめ】Rustのモジュールでコードを構造化しよう
今回はRustのモジュールの使い方について、基本的なところからファイル分割まで、一通り見てきました。
mod
でモジュールを作り、pub
で公開し、use
で便利に使い、そしてファイルを分割して整理する。この流れを掴めれば、あなたのRustコードはもっと読みやすく、管理しやすくなるはずです。
最初は少し戸惑うかもしれませんが、実際に手を動かして、自分のコードをモジュールで整理してみるのが一番の近道です。コードがきれいになっていく感覚は、なかなか気持ちが良いものですよ!
モジュールを使いこなせるようになれば、Rustでのプログラミングが一段と楽しく、効率的になること間違いなしです。次は、複数のモジュールをまとめて配布可能にする「クレート」について学んでみると、さらに世界が広がるかもしれませんね。
さあ、自信を持って、モジュールを活用したRustプログラミングを楽しんでいきましょう!
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。