Rustにおけるモジュールシステムとパッケージ構造の理解

パッケージ、クレート、モジュールの関係

Rustでは、パッケージはCargoによるビルド・配布単位であり、1つのCargo.tomlファイルを含むディレクトリです。パッケージ内には、最大1つのライブラリクレートsrc/lib.rs)と、任意数のバイナリクレートsrc/main.rsまたはsrc/bin/*.rs)が存在できます。クレートは、実行可能プログラムまたは再利用可能なライブラリとしてコンパイルされる最小単位です。

$ cargo new dining-app --lib
     Created library `dining-app` package
$ tree dining-app
dining-app/
├── Cargo.toml
└── src/
    └── lib.rs

モジュール階層の定義

以下は、レストラン管理システムを例にしたモジュール設計です。モジュールはmodキーワードで宣言され、ネスト可能です。

// src/lib.rs
pub mod dining_area {
    pub mod reservations {
        pub fn open_table() {}
        pub fn cancel_booking() {}
    }
    
    pub mod service {
        pub fn place_order() {}
        pub fn deliver_dish() {}
        pub fn process_payment() {}
    }
}

この構造は、次のようなツリー形式で表現されます:

crate (dining_area)
 └── reservations
     ├── open_table
     └── cancel_booking
 └── service
     ├── place_order
     ├── deliver_dish
     └── process_payment

可視性制御とパス解決

デフォルトでは、モジュールやその内部要素はprivate(外部からアクセス不可)です。明示的にpubを付与することで公開できます。また、絶対パス(crate::...)と相対パス(self::super::)でスコープを指定できます。

pub mod dining_area {
    pub mod reservations {
        pub fn open_table() {}
        fn internal_check() {} // private
    }
}

pub fn start_service() {
    // 絶対パスによる呼び出し
    crate::dining_area::reservations::open_table();
    // 相対パス(同一レベルのモジュール参照)
    dining_area::reservations::open_table();
}

サブモジュール内で親モジュールの非公開アイテムにアクセスする場合、super::を用います:

mod kitchen {
    fn prepare_meal() {}
    
    pub mod workflow {
        pub fn initiate() {
            super::prepare_meal(); // 親モジュールのprivate関数を呼び出し
        }
    }
}

列挙型の変種(variants)はデフォルトでpubであるため、外部から直接使用可能です:

pub mod menu {
    pub enum DishType {
        Starter,
        MainCourse,
        Dessert,
    }
}

use crate::menu::DishType;

fn order() {
    let first = DishType::Starter;
    let main = DishType::MainCourse;
}

useによる名前空間の短縮

use文は、頻繁に使うパスをローカルスコープに導入し、冗長な記述を削減します。

pub mod dining_area {
    pub mod reservations {
        pub fn open_table() {}
    }
}

use crate::dining_area::reservations;

pub fn handle_booking() {
    reservations::open_table();
    reservations::open_table();
}

さらに、関数まで展開することも可能です:

use crate::dining_area::reservations::open_table;

pub fn handle_booking() {
    open_table();
    open_table();
}

標準ライブラリの利用例:

use std::collections::HashMap;

fn manage_orders() -> HashMap {
    let mut orders = HashMap::new();
    orders.insert(101, "Grilled Salmon".to_string());
    orders
}

名前の衝突回避:asによるエイリアス

異なるモジュールから同名の型や関数をインポートする際は、asで別名を付与します。

use std::fmt::Result as FormatResult;
use std::io::Result as IoResult;

fn format_data() -> FormatResult<()> { /* ... */ }
fn read_input() -> IoResult<String> { /* ... */ }

再エクスポート:pub use

モジュールの内部構造を隠蔽しつつ、便利なエントリポイントを提供するためにpub useを使います。

pub mod dining_area {
    pub mod reservations {
        pub fn open_table() {}
    }
}

// 外部ユーザーが直接`dining_area::open_table()`と書けるように再エクスポート
pub use dining_area::reservations::open_table;

pub fn welcome_guest() {
    open_table(); // ローカルスコープで利用可能
}

外部クレートの統合

Cargo.tomlに依存関係を追加し、useで利用します。

[dependencies]
serde = { version = "1.0", features = ["derive"] }
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Order {
    id: u32,
    items: Vec<String>,
}

ネストされたuseパス

複数の関連パスをまとめてインポートできます。

// 通常の書き方
use std::io;
use std::io::Write;
use std::fs::File;

// 同等のネスト記法
use std::{io::{self, Write}, fs::File};

ワイルドカードインポート(注意が必要)

*によるグローバルインポートは、名前空間の混濁を招くため、ライブラリの公開APIでは推奨されません。

use std::collections::*; // 非推奨(明示的なインポートを優先)

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

大規模なプロジェクトでは、モジュールを別ファイルに分割します。

// src/lib.rs
pub mod dining_area;
pub use dining_area::reservations::open_table;

// src/dining_area.rs
pub mod reservations {
    pub fn open_table() {}
}

タグ: rust Modules crates cargo visibility

6月12日 19:40 投稿