- 大規模なプログラムでは、コードの整理が重要になる
- 方法:関連する機能をグループ化し、機能ごとにコードを分ける
- 実装の詳細をカプセル化する
- より高いレベルでコードを再利用
- パブリックかプライベートな変更する権利
- スコープの概念もある
- コードの構成を管理するための機能
- パッケージ:クレートを構築、テスト、共有できるCargo機能
- クレート:ライブラリまたは実行可能ファイルを生成するモジュールツリー
- モジュールと
use:パスの編成、スコープ、プライバシーを制御できる - パス:構造体、関数、モジュールなどのアイテムに名前を付ける
-
クレート
- バイナリやライブラリ
- ルートは、コンパイラが起動するソースファイルで、クレートのルートモジュールで構成
-
パッケージ
- 一連の機能を提供する 1 つ以上のクレート
- どのように構築するかを記述した Cargo.toml ファイルが含まれてる
- 規則
- 0 個または 1 個のライブラリクレートが含まれていなければならない
- バイナリクレートは好きなだけ含めることができるが、少なくとも 1 つのクレート (ライブラリまたはバイナリのいず>れか) を含める必要がある
-
パッケージを作成。
cargo newコマンドを入力- 作成したときに何が起こるか確認していく
$ cargo new my-project
$ cd my-project/
$ tree
-
cargoがCargo.tomlファイルを作成し、パッケージを提供 -
Cargo.tomlの内容を見ると、src/main.rsの記述ないsrc/main.rsはパッケージと同じ名前のバイナリクレートのクレートルートだから- パッケージディレクトリに
src/lib.rsがある場合、パッケージはパッケージと同じ名前のライブラリクレートを含んでおり、src/lib.rsがクレートルートであると認識している - ライブラリやバイナリをビルドするために、クレートルートファイルを rustc に渡す
-
my-projectはsrc/main.rsだけを含むパッケージがあり、my-projectのバイナリクレートだけが含まれている- パッケージが
src/main.rsとsrc/lib.rsを含む場合、ライブラリとバイナリの二つのクレートを持ち、両方ともパッケージと同じ名前になる- パッケージは、ファイルを
src/binディレクトリに配置することで、複数のバイナリのクレートを持つことができる- 各ファイルは別々のバイナリクレートになる
- パッケージは、ファイルを
- パッケージが
-
クレートは、関連する機能をスコープ内にまとめ、複数のプロジェクト間で機能を共有しやすくする
- ex)
randクレートは、乱数を生成する機能を提供randクレートをプロジェクトのスコープに組み込むことで、その機能を使用できる
- ex)
-
クレートの機能を独自のスコープに入れておくことで、特定の機能がクレートで定義されているのか、
randクレートで定義されているのかが明確になり、競合の可能性を防ぐことができる- ex)
rand クレートはRngトレイトを提供- 独自のクレートで
Rngという名前の構造体を定義できる - クレートの機能は独自のスコープ内で名前空間が設定されているため、依存関係として
randを追加しても、コンパイラはRngが何を参照しているのかを混乱させない - このクレートでは、定義した
Rng構造体を参照しているrandクレートからRngトレイトにアクセスするにはrand::Rngを使用
- 独自のクレートで
- ex)
- モジュールはクレート内のコードをグループにして、読みやすく再利用しやすいようにしてくれる。また、プライバシーも制御してくれる
- ex) レストランの機能を提供するライブラリクレートを作成
- 関数のシグネチャを定義。コードの構成のみ
- ex) レストランの機能を提供するライブラリクレートを作成
- クレートを構造化するには、機能を入れ子になったモジュールに整理
cargo new --lib restaurantを実行し、restaurantライブラリを作るsrc/lib.rsにて作成
$ cargo new --lib restaurant
$ cd restaurant/
$ tree
.
├── Cargo.toml
└── src
└── lib.rs
src/lib.rs
// src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
- モジュール
modキーワードで始める- モジュールの本文は
{}で囲む - モジュールの中にモジュールも入れられる
- 造体、列挙型、定数、特性、関数を定義できる
- 全ての定義を読むよりもグループに基づいてコードをナビゲートできるので、定義を見つけることが簡単になる
- コードをどこに配置すれば良いのかもしれる
- クレートルート
src/main.rsとsrc/lib.rs
- モジュールツリー構造
- ファイルシステムのディレクトリと同じように、モジュールを使ってコードを整理
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
- パスには2つの形式がある
- 絶対パス:クレート名またはリテラルクレートを使用して、クレートのルートから開始
- 相対パス:現在のモジュールから開始し、
self・superまたは現在のモジュール内の識別子を使用
- 絶対パスと相対パスの後には、
::で区切られた1つ以上の識別子が続く restrantライブラリにある関数をを絶対パスと相対パスで呼び出せるように変更
// src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
- 絶対パス
crateの後に::でパスを指定する
- 相対パス
- 名前で始まり、
::でパスを指定する
- 名前で始まり、
- 相対パスを使うか絶対パスを使うかは、プロジェクトに応じて決める
- その決定は、アイテム定義コードをアイテムを使用するコードとは別に移動させるか、一緒に移動させるかに依存する
- ex)
- 絶対パス×、相対パス○
front_of_houseモジュールとeat_at_restaurant関数をcustomer_experienceモジュールに移動した場合
- 絶対パス○、相対パス×
eat_at_restaurant関数を別のdiningモジュールに移動した場合
- 絶対パス×、相対パス○
Rustにおけるプライバシーの仕組み- すべての項目(関数、メソッド、構造体、列挙型、モジュール、定数)がデフォルトでプライベートになっている
- 内部の実装の詳細を隠すことがデフォルト
- 親モジュール内のアイテムは子モジュール内のプライベートなアイテムを使用することはできない
- 子モジュール内のアイテムは正規祖先モジュール内のアイテムを使用することができる
- 理由
- 子モジュールは実装の詳細をラップして隠している
- 子モジュールはそれらが定義されているコンテキストを見ることができる
- アイテムを
pubキーワードを使用することで子モジュールのコードの内部部分を外部の祖先モジュールに公開することができる
- すべての項目(関数、メソッド、構造体、列挙型、モジュール、定数)がデフォルトでプライベートになっている
eat_at_restaurant関数が、子モジュールのadd_to_waitlist関数にアクセスできるように、hostingモジュールにpubキーワードをつける- 一度、ビルドしてみるが、エラーになる
// src/lib.rs
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
- モジュールの
pubキーワードはその祖先のモジュールのコードだけを参照できるadd_to_waitlist関数がプライベートであることを示す- プライバシーのルールはモジュールだけでなく構造体、列挙型、関数、メソッドにも適用される
add_to_waitlistにもpubキーワードを付与
// src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
- パスの先頭に
superを使用することで、親モジュールから始まる相対パスを構築することもできる fix_incorrect_order関数はsuperで始まるserve_orderへのパスを指定してserve_order関数を呼び出すsuperを使って、親モジュール(下記の場合はルートであるcrate)に行くことができる- お互いに同じ関係にあり、クレートのモジュールツリーを再編成する場合には、一緒に移動する可能性が高い
- このコードが別のモジュールに移動した場合に、コードを更新する場所が少なくなるように
superを使用
- このコードが別のモジュールに移動した場合に、コードを更新する場所が少なくなるように
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
pubを使用して構造体やenumsをpublicに指定できる- 構造体の定義の前に
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() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
enumをpublic- その変種はすべて`publicになる
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
useキーワード- 絶対パスか相対パスどちらを選択しても、パスを毎回書かなけれならなかった
- パス内のアイテムをローカルアイテムにあるかのようにできる
- シンボリックリンクを作成するのと似ている
- 使用時にスコープに持ち込まれるパスは、他のパスと同様にプライバシーもチェックする
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
- use と相対パスを使ってアイテムをスコープにもできる
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
- 関数までスコープに入れてみる
- 相対パスや絶対パスで実行した例と達成するものは同じになるが、どこで関数が定義されているか不明瞭になる
- フルパスの繰り返しを最小限に抑えながら、関数がローカルに定義されていないことを明確にする必要がある
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}
- 構造体や列挙型などを使いながら持ち込む場合、フルパスを指定するのがイディオム
- 標準ライブラリの
HashMap構造体をバイナリクレートのスコープに持ち込むイディオムを例にしてみる
- 標準ライブラリの
// src/main.rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
- 同じ名前を持ちながらも親モジュールが異なる 2 つの結果型をスコープに持ち込む方法
- 親モジュールを使用することで2つの
Resultが区別される - 代わりに
use std::fmt::Resultとuse std::io::Resultを指定した場合、同じスコープ内に2つのResultが存在し、どちらを意味しているのか分からなくなる
- 親モジュールを使用することで2つの
// src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
- 同じ名前の2つの型を同じスコープに持ち込む問題の解決策がある
asで新しいローカル名もしくはエイリアスを指定する
- 2つの
Result型のうちの1つをasを使ってリネームする例
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
useキーワードで名前をスコープにすると、新しいスコープで使用可能な名前はprivateになるpub use- 他がそのアイテムをスコープにするため、再エクスポートと呼ばれる
- プログラマがドメインについてどのように考えるか異なる時に便利になる
- スコープ外から
hosting::add_to_waitlistで関数を呼び出せるようになる。新しいパスを呼び出せるようになる
- 他がそのアイテムをスコープにするため、再エクスポートと呼ばれる
// src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
-
Rustコミュニティのメンバーがcrates.ioでパッケージを公開してくれている -
パッケージに取り込むには、
Cargo.tomlファイルにパッケージをリストアップし、useを使ってアイテムをスコープに入れる -
標準ライブラリ (std) もパッケージの外部にあるクレートであることに注意
- 標準ライブラリは Rust 言語に同梱されているので、
Cargo.tomlを変更する必要はないが、アイテムをパッケージのスコープに入れるためにuseを使う必要がある
- 標準ライブラリは Rust 言語に同梱されているので、
use std::collections::HashMap;
- 同じモジュールで定義された複数のアイテムを使っている場合、それぞれのアイテムをそれぞれの行にリストアップすると、ファイルの縦方向のスペースを多く取る
use std::io;
use std::cmp::Ordering;
- 同じアイテムを1行でスコープに入れるために入れ子になったパスを使うことができる
- パスの共通部分を指定し
{}で囲む
use std::{cmp::Ordering, io};
- 大規模なプログラムでは、パスを使って同じパッケージやモジュールから多くのアイテムをスコープにして持ってくることで、必要な個別の
use文の数を大幅に減らすことができる - サブパスを共有する2つの
useステートメントを組み合わせるときに便利
use std::io;
use std::io::Write;
- 2つのパスの共通部分は
std::io、一つのuseステートメントに合わせる場合、selfを利用すると良い
use std::io::{self, Write};
- すべてのパブリックアイテムをスコープに入れたい場合は、パスの後にglob演算子
*を利用- 注意:どの名前がスコープ内にあるのか、プログラムで使用されている名前がどこで定義されているのかがわかりにくくなる
glob演算子は、テスト時によく使用され、テスト対象のすべてをtestsモジュールに取り込むことができる
use std::collections::*;
- モジュールが大きくなるとき、コードをナビゲートしやすくするため、別ファイルに移動させたい場合がある
- 前回まで利用していた
restrauntプロジェクトのfront_of_houseモジュールをsrc/front_of_house.rsに移動させてみる- クレートのルートファイルは
src/lib.rs src/main.rsであるバイナリクレートでも動作する
- クレートのルートファイルは
src/librsの変更
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
src/front_of_house.rsファイル作成
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
hostingモジュールのみ切り出してみるsrc/front_of_houseディレクトリ作成src/front_of_house/hosting.rsファイル作成src/front_of_house.rsのhostingモジュールを読み込ませるように変更src/lib.rsのpub use crate::front_of_house::hostingは変更しなくても良い
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
fn seat_at_table() {}