コンテンツにスキップ

Rustのモジュール

ここまでの例は、すべてひとつの main.rs ファイルに収まっていました。小さなプログラムならそれで十分ですが、実際のプロジェクトではコードを意味のあるまとまりに分けていきます。そのための Rust の仕組みがモジュールシステム、つまり mod、pub、use です。

モジュール(module)とは、関連するコードをひとまとめにした、名前付きのブロックです。

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

実行結果:

5
▶ 実際に試してみよう

ポイントは2つです。まず、関数はモジュール名をパスとして使い、math::add(...) のように呼び出します。そして、関数に pub が付いています。pub がないと、モジュールの外からは呼び出せません。

意外に思う人が多いので、ここははっきり書いておきます。モジュールの中身はデフォルトで非公開(private)です。上の例から pub を外すと、コンパイルが通らなくなります。

mod math {
fn add(a: i32, b: i32) -> i32 { // `pub` がない
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ エラー:関数 `add` は非公開
println!("{sum}");
}

モジュールの外から使ってほしいものには、pub を明示的に付ける必要があります。これはわざとそうなっています。モジュールの内部だけで使う補助関数は隠しておき、公開 API として見せたい部分だけを外に出せるようにするためです。

モジュールの中に、さらにモジュールを入れることもできます。大きなプロジェクトは、こうやって木のような構造を作っていきます。

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ 実際に試してみよう

毎回 shapes::circle::area(...) と書くのは面倒です。use を使うとパスをスコープに持ち込めるので、短い名前で直接呼べるようになります。

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // shapes::circle::area より短い
println!("{area}");
}
▶ 実際に試してみよう

じつは、use はもう使ったことがあります。HashMap のページに出てきた use std::collections::HashMap; と同じキーワードです。

モジュールをファイルに分ける

Section titled “モジュールをファイルに分ける”

上の例ではすべてをひとつのファイルに書きました。Rust Playground ではそうするしかないからです。自分のパソコンで作る実際のプロジェクトでは、モジュールごとにファイルを分けるのがふつうです。たとえば math モジュールがあるプロジェクトなら、次のような構成になります。

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // Rust に「このモジュールのコードは math.rs にある」と伝える
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math;(中身を書かず、セミコロンで終わる形)と書くと、Rust は同じディレクトリにある math.rs からモジュールの中身を読み込みます。プロジェクトがひとつのファイルに収まらなくなったら、すぐにこの書き方を使うことになります。

convert というモジュールを作り、2つの公開関数を持たせてください。c_to_f(摂氏から華氏へ)と f_to_c(その逆)です。main から 100°Cは212°Fです と 212°Fは100°Cです を表示します。

ヒント:公式は c * 9.0 / 5.0 + 32.0 と (f - 32.0) * 5.0 / 9.0 です。pub を忘れずに。関数は convert::c_to_f(100.0) のように、モジュール名を付けて呼び出します。

解答を見る
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°Cは{}°Fです", convert::c_to_f(100.0));
println!("212°Fは{}°Cです", convert::f_to_c(212.0));
}

実行結果:

100°Cは212°Fです
212°Fは100°Cです
▶ 実際に試してみよう

どちらの関数にも pub が付いているので、モジュールの外にある main から呼び出せます。どちらかの pub を消すと、その関数は非公開だとコンパイラが教えてくれます。

入れ子のモジュール school::grades を作り、公開関数 letter(score: u32) -> char を持たせてください。90以上なら 'A'、80以上なら 'B'、それ以外なら 'C' を返します。grades の中には、letter が使う非公開のヘルパー関数 is_at_least(score: u32, min: u32) -> bool も書きます。use で school::grades をスコープに持ち込み、95、85、70の評価を表示してください。

ヒント:school の中の mod grades と fn letter にはどちらも pub が必要ですが、is_at_least には付けません。同じモジュールの中のコードなら、非公開の関数もいつでも呼び出せます。

解答を見る
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// 非公開:`grades` の中のコードからしか呼べない
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

実行結果:

95 -> A
85 -> B
70 -> C
▶ 実際に試してみよう

letter が is_at_least を呼び出せるのは、同じモジュールの中にあるからです。main から grades::is_at_least(95, 90) を呼ぶと、“function is_at_least is private”(関数 is_at_least は非公開)というエラーになります。これがねらいです。モジュールは小さな公開APIだけを見せて、ヘルパー関数は自分の中にしまっておけます。

  • mod name { ... } で、関連するコードを名前付きのモジュールにまとめる。
  • モジュールの中身はデフォルトで非公開。モジュールの外に見せたいものには pub を付ける。
  • use でパスをスコープに持ち込めば、毎回フルパスを書かずに済む。
  • 複数ファイルのプロジェクトでは、mod name;(中身なし)と書くと、同じ名前のファイルからモジュールのコードが読み込まれる。
  • モジュールの中のコードは、そのモジュールの非公開の要素をいつでも使える。ヘルパーは非公開のままにして、呼び出す側に必要なものだけを公開しよう。

理解度チェック

1. mod の中に書いた要素は、デフォルトでは公開と非公開のどちらですか?

2. モジュールのコードをその場に書かず、math.rs から読み込ませるにはどう書きますか?

スコア: 0 / 2