English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Rustの組織管理

どんなプログラミング言語であれ、コードを組織できないと深く理解するのは難しいです。ほとんどのソフトウェア製品は単一のソースファイルからコンパイルされたものではありません。

このチュートリアルでは、これまですべてのプログラムが1つのファイルで書かれており、Rust言語の文法や概念を学ぶために便利にするためです。

プロジェクトにとって、コードの組織は非常に重要です。

Rustには箱、パッケージ、モジュールの3つの重要な組織概念があります。

箱(Crate)

「箱」はバイナリプログラムファイルまたはライブラリファイルで、パッケージ内に存在します。

「箱」は木構造で、その木の根はコンパイラが実行を開始したときにコンパイルされたソースファイルからコンパイルされたプログラムです。

注意:「バイナリプログラムファイル」は「バイナリ実行ファイル」ではない可能性があります。ただし、ターゲットマシンの言語を含むファイルであることが確定し、ファイル形式はコンパイル環境によって異なります。

パッケージ(Package)

Cargoを使用してnewコマンドを実行してRustプロジェクトを作成すると、プロジェクトディレクトリにはCargo.tomlファイルが作成されます。プロジェクトの本質はパッケージであり、パッケージはCargo.tomlファイルで管理され、このファイルはパッケージの基本情報および依存関係を記述します。

パッケージには最大1つのライブラリ「箱」が含まれることができますが、任意の数のバイナリ「箱」を含むことができます。ただし、少なくとも1つの「箱」を含む必要があります(それがライブラリであれバイナリ「箱」であれ)。

cargo newコマンドでパッケージを作成した後、srcディレクトリにはmain.rsのソースファイルが生成されます。Cargoはこのファイルをデフォルトでバイナリボックスのルートとし、コンパイルされたバイナリボックスはパッケージ名と同じです。

モジュール(Module)

ソフトウェア工学において、私たちは通常、使用しているプログラミング言語の組織規範に従って組織します。組織モジュールの主要な構造は木です。Javaの組織機能モジュールの主要なユニットはクラスであり、JavaScriptのモジュールの主要な方法はfunctionです。

これらの進んだ言語の組織ユニットは階層的に含まれることができます。ファイルシステムのディレクトリ構造のように。Rustの組織ユニットはモジュール(Module)です。

mod nation {
    mod government {
        fn govern() {}
    }
    mod congress {
        fn legislate() {}
    }
    mod court {
        fn judicial() {}
    }
}

これは法治国家を説明する手順です:国(nation)には政府(government)、議会(congress)、裁判所(court)があり、それぞれ行政、立法、司法の機能があります。それを木構造に変換できます:

国
 ├── 政府
 │ └── 統治
 ├── 議会
 │ └── 立法
 └── 裁判所
   └── 司法

ファイルシステムでは、ディレクトリ構造は通常、パス文字列でオブジェクトの場所を表すためのスラッシュで示されます。Rustのパス区切り記号は::です。

パスは絶対パスと相対パスの二種類に分けられます。絶対パスはcrateキーワードから始めて説明します。相対パスはselfまたはsuperキーワードまたは識別子から始めて説明します。例えば:

crate::nation::government::govern();

これはgovern関数の絶対パスを示しています。相対パスは以下のように表現できます:

nation::government::govern();

今では、ソースプログラム内で似たようなモジュール構造を定義し、メイン関数でパスを使用して試してみることができます。

これを行ったら、間違いがある部分を見つけることでしょう:governmentモジュールとその関数はすべてプライベート(private)で、アクセスすることは許可されていません。

アクセス権限

Rustには、二つのシンプルなアクセス権があります:パブリック(public)とプライベート(private)。

デフォルトでは、修飾子が付いていない場合、モジュール内のメンバーのアクセス権はプライベートです。

パブリックな権限を使用するには、pubキーワードを使用する必要があります。

プライベートなモジュールに対して、同レベルまたは下位の位置でしかアクセスできず、外部からアクセスすることはできません。

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    mod congress {
        pub fn legislate() {}
    }
    
    mod court {
        fn judicial() {
            super::congress::legislate();
        }
    }
}
fn main() {
    nation::government::govern();
}

このプログラムはコンパイルできます。courtモジュール内のsuperのアクセス方法に注意してください。

もしモジュール内に構造体が定義されていれば、構造体自体はプライベートですが、フィールドもデフォルトでプライベートです。したがって、モジュール内の構造体およびそのフィールドを使用するには、pub宣言が必要です:

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}
pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);
}
fn main() {
    eat_at_restaurant()
}

実行結果:

I'd like Wheat toast please

列挙型の列挙項目はフィールドを内包できますが、似たような性質はありません:

mod SomeModule {
    pub enum Person {
        King {
            name: String
        },
        Quene
    }
}
fn main() {
    let person = SomeModule::Person::King {
        name: String::from("Blue")
    };
    match person {
        SomeModule::Person::King { name } => {
            println!("{}", name);
        }
        _ => {}
    }
}

実行結果:

Blue

見つけにくいモジュール

Javaを使用した開発者は、最外層のclassブロックを非常に嫌がることが多いです——ファイル名と同じ名前で、ファイルのコンテナを示しているからです。それでも、煩雑ではありますが、"このクラスはファイルに含まれるクラスです"と強調するために一度書く必要があります。

ただし、これにはいくつかの利点があります:少なくとも、開発者がクラスの包装の存在に明確に気づくことができますし、クラスの継承関係を明確に記述できます。

Rustでは、モジュールはJavaのクラスの包装のようなものですが、ファイルの最初にメイン関数を書けるので、どうしてそれが説明できるのでしょうか?

それぞれのRustファイルの内容は「見つけにくい」モジュールです。

この点を示すために、二つのファイルを使ってみましょう:

main.rs ファイル

// main.rs
mod second_module;
fn main() {
    println!("This is the main module.");
    println!("{}", second_module::message());
}

second_module.rs ファイル

// second_module.rs
pub fn message() -> String {
    String::from("This is the 2nd module.")
}

実行結果:

This is the main module.
This is the 2nd module.

use キーワード

use キーワードを使うと、モジュール識別子を現在の作用域に導入できます:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}
use crate::nation::government::govern;
fn main() {
    govern();
}

このプログラムはコンパイルできます。

useキーワードはgovern識別子を現在のモジュールにインポートするため、直接使用できます。

これでローカルモジュールパスが長い問題が解決されます。

もちろん、同じ名前が2つ存在し、インポートが必要な場合、asキーワードを使用して識別子に別名を追加することができます:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub fn govern() {}
}
    
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;
fn main() {
    nation_govern();
    govern();
}

ここにはgovern関数が2つあります。1つはnation下、もう1つはgovernment下です。nation下のものをasを使ってnation_governという別名で呼び出します。2つの名前を同時に使用できます。

useキーワードはpubキーワードと一緒に使用できます:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub use government::govern;
}
fn main() {
    nation::govern();
}

標準ライブラリの参照

Rust公式標準ライブラリ辞書:https://doc.rust-lang.org/stable/std/all.html

本章の概念を学んだ後、システムライブラリを簡単にインポートしてプログラムを開発することができます:

use std::f64::consts::PI;
fn main() {
    println!("{}", (PI / 2.0).sin());
}

実行結果:

1

すべてのシステムライブラリモジュールはデフォルトでインポートされており、使用する際にはuseキーワードを使用してパスを短縮して便利に使用できます。