Rust の pub キーワード

可視性修飾子の完全ガイド — モジュール境界と最小公開原則 · 作成 2026-05-18
目次
  1. pub とは何か(一言)
  2. Rust のモジュールシステムと可視性
  3. 可視性修飾子の一覧と比較表
  4. 可視性の範囲図(SVG)
  5. 具体例:モジュール構造で確かめる
  6. 最小公開原則 — 実務的な指針
  7. scandata プロジェクトにおける pub の使い方
  8. よくある間違いと落とし穴
  9. 関連項目

1. pub とは何か(一言)

pub「このアイテムを現在のモジュールの外からも使えるようにする」 キーワードです。

Rust では デフォルトがすべて非公開(private) です。 pub を書かない限り、関数・構造体・フィールド・型エイリアスはそのモジュール内でしか見えません。 これは Go や Java の「デフォルト公開」とは逆の設計思想で、意図しない API 漏洩を防ぐために選ばれています。

// pub なし → モジュール外からは存在すら見えない
fn internal_helper() { }

// pub あり → モジュール外から呼び出せる
pub fn open_tsm(path: &std::path::Path) -> Result<TsmData, TsmError> { /* ... */ }

2. Rust のモジュールシステムと可視性

Rust の可視性はすべて モジュールツリー を基準に定義されます。 ファイル = モジュールではなく、mod 宣言によるネストが単位です。

// src/lib.rs
pub mod io;       // io モジュールを外部に公開
mod internal;     // internal モジュールは crate 内のみ

// src/io/mod.rs
pub mod tsm;      // tsm サブモジュールを公開
mod shared;       // 内部ヘルパー(非公開)

// src/io/tsm.rs
pub fn open(path: &std::path::Path) -> Result<TsmData, TsmError> {
    // この関数は io::tsm::open として外部から呼べる
}
重要: 関数に pub を付けても、その関数が属する mod 自体が pub でなければ 外部からアクセスできません。可視性はモジュールツリーを通じて「伝播」します。

3. 可視性修飾子の一覧と比較表

(なし)
同モジュールのみ
pub(super)
親モジュールまで
pub(in path)
指定モジュールまで
pub(crate)
crate 全体
pub
外部 crate も含む全体
書き方 アクセス可能な範囲 使いどころ
(修飾子なし) 同じモジュール内のみ 内部実装の隠蔽。デフォルト。積極的に使う
pub(self) 同じモジュール内のみ(修飾子なしと同じ) 明示的に書きたい場合のみ。通常は省略
pub(super) 親モジュールまで サブモジュール間で共有するが外に漏らしたくない実装
pub(in path) 指定した祖先モジュールまで サブシステム内で共有するピンポイント公開(使用頻度低)
pub(crate) 同一 crate 内のすべてのモジュール ライブラリ内部 API の標準。外部に漏らしたくないが crate 内で共用する
pub 外部 crate を含む全体 ライブラリの公開 API。意図してユーザに使ってもらう関数・型のみ

4. 可視性の範囲図

外部 crate(pub のみアクセス可) crate(pub + pub(crate) アクセス可) mod parent(pub + pub(crate) + pub(super) アクセス可) pub(in path) で指定した祖先モジュールまで mod child(定義されたモジュール) 定義されたアイテム (なし) — 同モジュールのみ pub(super) — 親まで pub(crate) — crate 全体 pub — 外部 crate も含む全体 最小公開原則: 1. まず「なし」(private)で書く 2. crate 内で必要なら pub(crate) に昇格 3. 外部 API として提供するなら pub に昇格 pub(super) の典型ユースケース: 子モジュールのテストヘルパーを 親テストモジュールから呼ぶ場合
図1 — Rust の可視性レベルとアクセス範囲

5. 具体例:モジュール構造で確かめる

5-1. 基本的な可視性の動作

mod outer {
    pub fn visible_outside() {}   // outer の外から呼べる
    fn only_in_outer() {}         // outer 内のみ

    pub(crate) fn crate_wide() {}  // crate 内ならどこでも

    mod inner {
        pub(super) fn to_outer() {}  // outer からは呼べる、crate の他からは不可
        fn private() {}               // inner 内のみ
    }
}

fn main() {
    outer::visible_outside();      // OK
    outer::crate_wide();            // OK(同一 crate 内)
    // outer::only_in_outer();      エラー: private
    // outer::inner::to_outer();    エラー: pub(super) は inner の親まで
}

5-2. 構造体フィールドの可視性

構造体のフィールドは 構造体自体とは独立に可視性を持ちますNewtype パターンでよく問題になる「外側 pub / 内側 pub」の違いも同じ仕組みです。

pub struct TsmHeader {
    pub frame_count: u32,     // 外部から読み書きできる
    pub(crate) width: u16,    // crate 内でのみ読み書きできる
    checksum: u32,            // 外部から見えない(内部整合性チェック用)
}

// 外部 crate から:
let h: TsmHeader = tsm_parser::parse(bytes)?;
let n = h.frame_count;  // OK: pub
// let w = h.width;      エラー: pub(crate)
// let c = h.checksum;   エラー: private

5-3. pub use による再エクスポート

pub use を使うと、内部の深い場所にある型を、ライブラリのトップレベルに「引き上げて」公開できます。

// src/lib.rs
mod io;
mod domain;

// 内部モジュール構造を隠しつつ、使いやすい API を提供
pub use domain::value_object::TsmData;
pub use io::tsm::open_tsm;

// 使う側: scandata_net::open_tsm(...) と書ける(io::tsm:: を知らなくてよい)

6. 最小公開原則 — 実務的な指針

鉄則: まず 非公開(修飾子なし) で書き、必要になった時だけ昇格させる。 公開範囲を後から広げるのは簡単だが、後から狭めると破壊的変更になる。

推奨フロー

ステップ状況対処
1 新しい関数・型を書く pub なし(private)からスタート
2 同じ crate 内の別モジュールから使いたくなった pub(crate) に昇格。外部に漏れない
3 テストモジュール(親)から呼びたい内部ヘルパー pub(super) に昇格。親より上には漏れない
4 ライブラリのユーザ(外部 crate)に使わせたい pub に昇格。これが「公開 API」
注意: pub にした瞬間から セマンティックバージョニングの対象 になります。 シグネチャを変えると semver major バンプが必要です。 pub(crate) であれば crate 内のリファクタは破壊的変更になりません。

7. scandata プロジェクトにおける pub の使い方

scandata の Rust 版(ScanDataNet)は Hexagonal Architecture を採用しています。 ドメイン層は副作用ゼロ、fs / io / tokio は Adapter 層に閉じ込める設計です。 この構造と pub の使い方は密接に関係します。

7-1. I/O 関数(Adapter 層)はなぜ pub

// src/io/tsm.rs(Adapter 層)
pub fn open_tsm(path: &std::path::Path) -> Result<TsmData, TsmError> {
    // バイナリ I/O はここだけ。ドメイン層は知らない
}

open_tsmpub であるべき理由:

7-2. 値オブジェクト・ドメイン型は pub(crate) から始める

// src/domain/value_object.rs
pub(crate) struct FramesData<S> {  // まず crate 内公開
    pub(crate) data: std::sync::Arc<ndarray::Array3<i16>>,
    pub(crate) tag: DataTag,
}

// 外部 API として提供することが決まったら pub に昇格:
// pub struct FramesData<S> { ... }

7-3. 可視性とレイヤ依存の対応

レイヤモジュール例推奨可視性理由
ドメイン(純粋関数) domain::modifier pub(crate) アプリ内で使うが外部に露出させない
I/O Adapter io::tsm pub アプリ起動 / テストから直接呼ぶエントリポイント
値オブジェクト(型定義) domain::value_object pub(外部 crate に渡す場合) WebSocket の RPC 型として外部に公開
内部ヘルパー io::tsm::parse_header 修飾子なし(private) 実装詳細。外部に見せる必要なし

8. よくある間違いと落とし穴

8-1. 構造体は pub なのにフィールドが private → 構築できない

// lib.rs で定義
pub struct TsmHeader {
    frame_count: u32,  // private フィールド
}

// 外部 crate から構造体リテラルで作ろうとするとエラー
let h = TsmHeader { frame_count: 10 };  // エラー: field is private

// 解決策: コンストラクタ関数を pub で提供する
impl TsmHeader {
    pub fn new(frame_count: u32) -> Self {
        Self { frame_count }
    }
}
フィールドを private にしてコンストラクタ経由で作らせると、バリデーションを強制できますNewtype パターンでも同じ手法が有効です。

8-2. mod が private なのに中の fn を pub にする意味はない

mod internal {          // pub なし → 外部から見えない
    pub fn helper() {}  // この pub は無意味(mod が見えないので)
}
// internal::helper() は外部 crate からアクセス不可

8-3. pub(crate) は crate 境界を越えない

// crate A で定義
pub(crate) fn secret() {}

// crate B(A を依存に持つ)からは呼べない
// a::secret();  エラー: function is pub(crate)

8-4. pub use の再エクスポートを忘れる

// src/lib.rs で mod だけ宣言して pub use を忘れると
mod io;  // io::tsm::open_tsm は外部から直接使えない

// pub use で引き上げる
pub use io::tsm::open_tsm;  // これで外部から使えるようになる