Progrust Library.

// dictionary

モジュールのファイル分割.

mod shop;のように本体({})を持たないモジュールの宣言は、その中身を外部ファイルから読み込みます1。ファイルの置き場所は既定でモジュールのパス(Path)をそのまま写した形になります1。ツリーの形を変えずにコードだけをファイルへ分けられるため、main.rsが膨らんできたときの整理手段になります。

modは他言語の#includeのようなテキスト取り込みではありません。ファイルを読み込むmod宣言はモジュールツリーの中で1回だけ書き、他のファイルからはそれが宣言された場所へのパスで参照します2

// project

買い物カートをファイルに分ける

3 files

Playgroundで開く
src/
shop/
cart.rs
main.rs
shop.rs
src/main.rs
// `shop`の中身を src/shop.rs から読み込む
mod shop;

fn main() {
    println!("税込合計: {}円", shop::cart::total(&[980, 1250])); // 2453円
}

shopcartの祖先になるので、cartのファイルはsrc/shop/ディレクトリの中に置きます。ツリーの形もpubによるプライバシー境界の効き方も、{}に直接書いた場合と変わりません。

src/shop.rs
// `cart`の中身を src/shop/cart.rs から読み込む
pub mod cart;

// 非公開のまま。shop自身とその子孫からは使える
const TAX_RATE: u32 = 10;
src/shop/cart.rs
pub fn total(prices: &[u32]) -> u32 {
    let subtotal: u32 = prices.iter().sum();
    // ファイルは分かれても、ツリー上の位置は変わらない
    subtotal * (100 + crate::shop::TAX_RATE) / 100
}

ファイルの置き場所

祖先のモジュールはディレクトリになり、モジュール自身の中身は「モジュール名 + .rs」のファイルに入ります1

モジュールパス ファイルパス そのファイルの中身
crate src/main.rs mod shop;
crate::shop src/shop.rs pub mod cart;
crate::shop::cart src/shop/cart.rs pub fn total

つまりsrc/の直下には、shop自身の中身であるshop.rsと、その子モジュールを入れるディレクトリshop/が並びます。役割が違うので、両方あってよいのです。

宣言は1回だけ

modは取り込みではなくツリー上の位置の宣言なので、1つのモジュールをツリーの2か所に置くことはできません。すでに読み込んであるコードを別のファイルから使いたいときに書くのは、modの再宣言ではなくuse宣言(あるいはcrate::superから始まるパスでの直接参照)です2

2つのスタイル

モジュールの中身は、モジュール名のディレクトリの中のmod.rsに置くこともできます1src/shop.rsスタイルが現在推奨される書き方で、src/shop/mod.rsスタイルは古い書き方ですが今も有効です2

内容 src/shop.rsスタイル src/shop/mod.rsスタイル
shop自身の中身 src/shop.rs src/shop/mod.rs
子モジュールcart src/shop/cart.rs src/shop/cart.rs

mod.rsスタイルの難点は、プロジェクト中にmod.rsという同名ファイルが大量にでき、エディタで同時に開いたときに見分けづらくなることです2。別々のモジュールで2つのスタイルを混在させること自体は許されますが、読み手が混乱するため避けるのが無難です2

補足

modを書き足したときのエラー

すでに別の場所で読み込んであるモジュールを、うっかりmodし直すと、コンパイラはその位置に対応するファイルを探しに行って見つけられず、E0583(file not found for module)になります。このエラーには「クレート内の他の場所にmodがあるならuse crate::...で取り込め」という助言が付きます(Rust 1.93で確認)。

#[path]属性で置き場所を変える

既定の対応関係から外れた場所にファイルを置きたい場合は、#[path]属性でパスを指定できます1

#[path = "legacy/shop_v1.rs"]
mod shop;

インラインモジュールの中で使う場合、相対パスの起点は、クレートルート(main.rslib.rsなど)やmod.rsといったmod-rsファイルと、それ以外のnon-mod-rsファイルとで変わります1。なお#[path]を使えば同じファイルを2か所から読み込めますが、その場合は中身が独立した2つのモジュールとして二重に定義されます。

ファイルを分ける単位の目安

ファイル分割はあくまで整理の手段で、クレートの公開APIの形とは独立です。まずインラインモジュールで構造を決め、ファイルが読みづらい大きさになった時点で切り出す進め方が扱いやすくなります。切り出したあとで公開APIの形だけを整えたい場合は再エクスポートと組み合わせます。

Footnotes

  1. Modules — The Rust Reference 2 3 4 5 6 7

  2. Separating Modules into Different Files — The Rust Programming Language 2 3 4 5

この辞書が使われているページ

backlinks 3

  1. 辞書モジュール
  2. 辞書再エクスポート
  3. 動かして学ぶRustプログラミング問題集 › 第15章 ファイル分割とクレート