Scrappy

覚え書き。

Rust:Separating Modules into Different Files

分割前

  • 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();
}

front_of_houseをsrc/front_of_house.rsに移動する。

  • src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
  • src/front_of_house.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}

モジュールツリー内で、mod定義は一度だけで良い。一度コンパイラがそのファイルがプロジェクトの一部であることを認識すると、他のファイルは参照する際に、そのパスを指定するだけで良い。言い換えるとmodは他の言語のincludeではない。

hostingモジュールを/src/front_of_house/hosting.rsに移動する

  • src/front_of_house.rs
pub mod hosting;
  • src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

Rust:Bringing Paths into Scope with the use Keyword

Bringing Paths Into Scope with the use Keyword - The Rust Programming Language

関数を呼び出す時にpathを全部書くのは不便だと感じるかもしれない。 useキーワードでpathへのショートカットを作成できる。

crate rootに、use crate::front_of_house::hosting;を追加した場合、そのスコープ内でhostingが有効になる。 useuseが記述されたスコープでのみ有効になるため、以下はエラーとなる。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}

問題を解決するには、usecustomerの中に移動させるか、super::hostingで親モジュールから参照させる。

慣用的なuseの使い方

useで関数をスコープに持ち込む場合は、関数名まで持ち込むのではなく親モジュールを持ち込む。 こうすることでその関数がどのモジュールで定義されているかが明確になる。

一方、構造体、enum、その他のitemを持ち込む場合は、フルパスを指定する。

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

このidiomに強い理由があるわけではないが、Rustのコードはこの形で読み書きされることに慣れている。 また、例外は同じ名前の2つのitemをスコープに持ち込む場合である。以下は異なる親モジュールを持つが、同じ名前のResult型をスコープに持ち込む方法である。

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
}

fn function2() -> io::Result<()> {
    // --snip--
}

asキーワードで新しい名前を与える

同じ名前の2つの型をスコープに持ち込む別の方法として、asキーワードで新しいローカル名をつけることができる。

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

fn function1() -> Result {
    // --snip--
}

fn function2() -> IoResult<()> {
    // --snip--
}

pub useによる名前の再公開

useーワードで名前をスコープに持ち込むと、その名前は新しいスコープ内で非公開(プライベート)になる。pubuseを組み合わせることで、その名前をあたかもそのコード内で定義されたかのように参照できるようにすることができる。この手法は「再公開(re-exporting)」と呼ばれる。

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();
}

pub useにする前にadd_to_waitlist()呼び出すには、外部コードはrestaurant::front_of_house::hosting::add_to_waitlist()と指定する必要があり、またfront_of_houseをpublicにする必要があった。pub useを使うことで、hostingモジュールが再公開されたため、restaurant::hosting::add_to_waitlist()で使用が可能になる。

外部パッケージの利用

randパッケージを使用する際、Cargo.tomlに rand = "0.8.5"を追加した。 Cargoはcrates.ioからrandパッケージとその依存関係をダウンロードし、我々のプロジェクトで使用できるようにする。

その後、randの定義をプロジェクトのスコープに取り込むために、以下のように use 文を追加する。ここでは、クレート名 rand を先頭に指定し、スコープに取り込みたいアイテムを列挙する。Rngトレイトをスコープに取り込み、rand::thread_rng 関数を呼び出す。

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}

標準ライブラリstdも我々のパッケージから見ると外部クレートの1つである。ただ、Rust言語に付属しているため、Cargo.tomlにstdを記載する必要はない。しかし、itemをスコープに取り込む場合は、useが必要になる。

use std::collections::HashMap;

ネストしたpathで巨大なuseリストを整理する

// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

以下のように書き換えることができる。

// --snip--
use std::{cmp::Ordering, io};
// --snip--

ネストしたpathは、pathのどのレベルでも使用できる。

use std::io;
use std::io::Write;

use std::io::{self, Write};

glob演算子

use std::collections::*;

glob演算子を使うと、どの名前がスコープ内にあり、プログラムで使用される名前がどこで定義されたのかが分かりづらくなるため、注意が必要である。

Rust:Paths for Referring to an Item in the Module Tree

Paths for Referring to an Item in the Module Tree - The Rust Programming Language

pathには2つの形式がある

  • 絶対パスはcrate rootからのフルパス。外部crateの場合は、crate名から始め、current crateの場合はcrateから始める

  • 相対パスselfsuperまたはcurrent module内の識別子を使用し、current moduleから始める

どちらも::で区切る。

相対パスを使用するか絶対パスを使用するかは、プロジェクトに応じて決定する。 項目を定義しているコードと、それを使用しているコードを別々に移動する可能性が高いか、一緒に移動する可能性が高いかによって決まる。 一般的には、コード定義と項目の呼び出しを独立して移動させる可能性が高いため、絶対パスを指定することを推奨する。

pubキーワードによるPathの公開

mod front_of_house {
    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();
}

hostingがprivateのためエラーとなる。Rustでは全てのitemはデフォルトで親モジュールからはprivateになる。関数や構造体のようなitemをprivateにしたい場合は、モジュール内に配置する。

親モジュールは子モジュールのプライベートitemは使用できない、しかし子モジュールはその祖先 のitemを使用することができる。

Rustがこのモジュールシステムを選択したのは、内部実装の詳細をデフォルトで隠蔽するためである。これにより、外部コードを破壊することなく、内部コードのどこを変更できるのかを知ることができる。 ただし、pubキーワードを使用して、子モジュールのitemに外部の祖先モジュールからアクセス可能にできる。

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();
}

mod hostingpubキーワドを付けただけでは、まだエラーとなる。moduleにmodキーワードを付けた場合、そのモジュールにアクセスできるだけで、内部コードにはアクセスできない。モジュールはコンテナなので、更にitemもpublicにする必要がある。 なお、eat_at_restaurantfront_of_houseは同じモジュール内に定義されている(兄弟関係)ため、pubは不要である。

Best Practices for Packages with a Binary and a Library

パッケージにはバイナリークレートとライブラリークレートの両方を含めることができる。一般的に、このようなパッケージパターンの場合は、バイナリークレートはライブラリークレートを呼び出すだけのコードにする。このようにすることでライブラリークレートのコードが共有できるため、多くの機能を他のプロジェクトで利用できる。

superによる相対パス

ファイルシステム..表記のように、superを使用することで、親モジュールからの相対パスを定義できる。

構造体とEnumをpublicにする

構造体とenumにもpubを使用した場合、その構造がpublicになるだけで、フィールドはprivateのままになる。それぞれのフィールドをpublicにするか否かを選択できる。

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");
}

privateフィールドを持つ構造体の場合、コンストラクタが必要である。なぜなら、privateフィールドには値をセットすることができず、インスタンを生成できないため。

対照的に、enumをpublicにした場合は、全てのvariantがpublicになる。

Rust:Defining Modules to Control Scope and Privacy

Defining Modules to Control Scope and Privacy - The Rust Programming Language

Modules Cheat Sheet

  • Start from crate root

コンパイラは、最初にcrate rootファイルを検査しコンパイルする。

  • module定義

crate rootファイルにmoduleを定義することができる。 "garden"moduleを定義した場合、コンパイラはmoduleを以下の場所から探す。

- インラインで記述された`mod module {}`
- src/garden.rs
- src/garden/mod.rs
  • submodule定義

crate rootとは別のファイルに、submoduleを定義することができる。 例えばsrc/garden.rsにmod vegetables;と定義した場合、コンパイラはsubmoduleを親moduleのディレクトリ名の中の以下の場所探す。

- インラインで記述された`mod vegetables{}`
- src/garden/vegetables.rs
- src/garden/vegetables/mod.rs
  • module内のコードへのPath

一度moduleがcrateの一部になると、プライバシールールに従う限り、crateと同様にどこからでも参照することが可能になる。 例えば、garden vegetables moduleにAsparagus型がある場合、crate::garden::vegetables::Asparagasで参照できる。

  • Private vs. public

module内のコードはデフォルトでは、親moduleからはプライベートになる。 moduleをpublicにするには、modの代わりにpub modを使用する。 public module内のitemをpublicにするする場合も、その定義の前にpubを使用する。

  • useキーワード

スコープ内で、useキーワードはitemへのショートカット作成する。 crate::garden::vegetables::Asparasugを参照できるスコープ内で、use crate::garden::vegetables::Asparagus;とすると、Asparagusだけで使用できる。

module内で関連するコードのグルーピング

module内には、さらに別のmoduleを配置することができる。またmoduleは構造体、enum、定数、トレイトや関数などの別のitemを配置することができる。

moduleツリーはファイルシステムディレクトリツリーと同じようなものである。

Rust:Packages and Crates

Packages and Crates - The Rust Programming Language

crateは、Rustコンパイラが1回に扱うコードの最小単位である。 単一のソースコードrustcコンパイルする際も、そのファイルをcrateとして扱う。 crateはmoduleを含むことができ、他のファイルに定義されているmoduleも、crateと合わせてコンパイルされる。

crateには2つの形式がある。

Binary crates

Library crates

  • main関数を持たず、実行可能な形ではコンパイルされない
  • 複数のプロジェクトで共有されるように機能を定義したもの
  • Rustaceansがcareteというときは、多くの場合Library cratesを指す

crate rootは、Rustコンパイラーが処理を開始するソース・ファイルのことであり、crateのroot moduleを構成する。

packageは1つ以上のcrateをまとめたもので、 一連の機能を提供する。 packageはビルド方法を定義したCargo.tomlファイルを含む。 Cargoもコマンドラインツールのbinary crateを含むpackageである。

Packageは複数のbinary crateを含むことができるが、library crateは最大で1つだけである。 packageには少なくとも1つのcrateが必要である。

binary crateの場合は、src/main.rsを、library crateの場合は、src/lib.rsをcrate rootと見なす。