Rust勉強メモ/クレートとモジュール
クレート
「プログラミングRust 第2版」p.153
クレートは次の物を含む
1つのライブラリか実行ファイルの全ソースコード
テストコード、example
ツール、設定
rlibファイル
「プログラミングRust 第2版」p.156
--crate-type libでコンパイルしてできる成果物
型情報を含む
インライン関数、ジェネリクス、マクロ等も含む
C言語でいうヘッダファイルとlibファイルを合わせたようなものか
エディション
「プログラミングRust 第2版」p.157
Cargo.tomlにedition = "2018"のように指定する。指定されなければ2015
エディションが混在しても良い
2015エディションのクレートが2021エディションのクレートに依存することすら可能
cargo fixで新しいエディションに対応するようにコードが自動で修正される
最適化とデバッグシンボルを両方有効にする
「プログラミングRust 第2版」p.157
code:Cargo.toml
debug = true
モジュール
「プログラミングRust 第2版」p.158
クレートはプロジェクト間のコード共有の機構
モジュールはプロジェクト内のコード構造化の機構(=名前空間)
モジュール=アイテムの集合
アイテム=関数、構造体、……
アイテムの可視性
「プログラミングRust 第2版」p.158
code:Rust
mod spores {
use cells::{Cell, Gene};
pub struct Spore { ... }
pub fn produce_spore(factory: &mut Sporangium) -> Spore { ... }
pub(crate) fn genes(spore: &Spore) -> Vec<Gene> { ... }
fn recombine(parent: &mut Cell) { ... }
}
https://gyazo.com/20384e41801247ffd78103c9ef14a330
?モジュールのネスト
「プログラミングRust 第2版」p.159
code:ネストされたモジュールの例.rs
mod plant_structures {
pub mod roots {
...
}
ネストされたモジュールを他のクレートからも使えるようにするには、そのモジュールの外側のモジュールもすべてパブリックにする必要がある。そうしないと、次のようなエラーが出る。
どういう意味?
pub mod plant_structuresとしないと、rootsの中で定義されたアイテムがエクスポートされない?
可視性の実験
code:Rust
pub mod rootmod {
pub mod submod1 {
fn one() -> i64 { 1 }
pub fn two() -> i64 { one() + 1 }
}
mod submod2 {
pub fn three() -> i64 {
// one() + 2 <-- error
3
}
}
}
one()は他クレートから使えない
rootmodのpubを無くすと、他のクレートからtwo()が使えなくなる
three()は "warning: function is never used: three" と警告される(エクスポートされてない?)
?ファイル分割の方法
「プログラミングRust 第2版」p.161
コンパイラはsrc/main.rsを読む。src/main.rsにmod foo;があると
src/foo.rs、src/foo/mod.rsを読む
どちらもあるとエラー
src/foo.rsにmod bar;(foo::bar)があると
src/foo/bar.rs、src/foo/bar/mod.rsを読む
どちらもあるとエラー
という認識でOK?
親モジュールのインポート
「プログラミングRust 第2版」p.163
サブモジュールは、親モジュールの名前空間を引き継がない
code:proteins/mod.rs
pub enum AminoAcid { ... }
pub mod synthesis;
code:proteins/synthesis.rs
use super::AminoAcid;
super::で親モジュールの名前空間をインポートできる
super:親モジュール
crate:現在のモジュールを含むクレート
self:現在のモジュール
絶対パス
「プログラミングRust 第2版」p.165
Cargo.tomlでimageクレートを依存関係に含めた状態で、mod imageを書いている場面を想定
use ::image::Pixels;
絶対パスは、常に外部クレートを表す
use self::image::Sampler;
imageモジュールのSamplerをインポートする
プレリュード
「プログラミングRust 第2版」p.165
Rustコンパイラはuse std::prelude::v1::*;が自動的に宣言されたかのように振る舞う
結果、Vec、Resultなどがモジュール名無しで使える
その他のpreludeモジュールは
*でインポートしてほしいということを利用者に示すだけの、単なる慣習
構造体のフィールドをpubにする
「プログラミングRust 第2版」p.166
code:Rust
pub struct Fern {
pub roots: RootSet,
pub stems: StemSet
}
プライベートフィールド:その構造体が定義されたモジュールと、サブモジュールからアクセス可
パブリックフィールド:モジュールの外側からもアクセス可
C++は、クラス単位の可視性制御
Rustは、モジュール単位の可視性制御
?staticと定数
「プログラミングRust 第2版」p.166
pub const ROOM_TEMPERATURE: f64 = 20.0;
pub static ROOM_TEMPERATURE: f64 = 68.0;
定数は、C++の#defineに似ていて、値を使うすべての場所に値がコンパイル時に埋め込まれる。
必ず、埋め込まれる?変数になることはない?
staticは、プログラムが実行を開始する前に用意され、終了するまで生き残る変数だ。
staticはmutを付ければ可変になる
unsafeからしか読み書きできないけど
src/binディレクトリ
「プログラミングRust 第2版」p.169
code:ファイル構造
fern_sim/
Cargo.toml
src/
bin/
efern.rs コマンドラインツール efern
draw_fern/ コマンドラインツール draw_fern
main.rs
draw.rs
lib.rs ライブラリのルートモジュール
efern、draw_fernというコマンドラインツールが生成される
cargo run --bin efern
属性
「プログラミングRust 第2版」p.170
#[allow(non_camel_case_types)]
#[cfg(target_arch = "x86_64")]
#[cfg(test)]
cfg(test)とtestは何が違うの?
main.rsやlib.rsの先頭に#![]で属性を書くと、クレート全体に適用される]
?インライン展開
「プログラミングRust 第2版」p.171
#[inline]を付けないとインライン展開が行われない場合がある。あるクレートに宣言されている関数やメソッドを別のクレートから使う場合、それが(型パラメータを持つ)ジェネリックであるか、#[inline]属性が付いていない限り、インライン展開は行われない。
クレートをまたぐとインライン展開がされなくなる?
LTOはなし?
cfg(test)とtestの違い
「プログラミングRust 第2版」p.174
#[test]はテストケースを表す関数に付ける
#[cfg(test)]はテスト時だけビルドされる、テストケースの実行に必要なアイテムに付ける
code:Rust
mod tests {
fn roughly_equal(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
fn trig_works() {
use std::f64::consts::PI;
assert!(roughly_equal(PI.sin(), 0.0));
}
}
#[cfg(test)]無しでは、通常ビルドの際にroughly_equalが利用されないという警告が出てしまう
?結合テスト
「プログラミングRust 第2版」p.175
srcと同じ階層のtestsディレクトリにテストコードを置くと、個々のテストを独立したクレートとしてコンパイルし、対象のライブラリとリンクされて実行される
独立したクレートとなる単位は?関数単位?ファイル単位?
code:tests/unfurl.rs
use fern_sim::Terrarium;
use std::time::Duration;
fn test_fiddlehead_unfurling() {
let mut world = Terrarium::load("tests/unfurl_files/fiddlehead.tm");
assert!(world.fern(0).is_furled());
let one_hour = Duration::from_secs(60 * 60);
world.apply_sunlight(one_hour);
assert!(world.fern(0).is_fully_unfurled());
}
docコメント
「プログラミングRust 第2版」p.175
code:Rust
/// Create and return a [VascularPath] which represents the path of
/// nutrients from the given [Root]r to the given [Leaf](leaves::Leaf). ///
/// = #[doc]:個別アイテムのドキュメント
//! = #![doc]:モジュールやクレートのドキュメント
ドキュメントはMarkdown
URLとしてRustのパスを書ける(leaves::Leaf、roots::Root)
#![doc = include_str!("../README.md")]とするとREADME.mdの内容がdocコメントに差し込まれる
docコメントのコード片とテスト
「プログラミングRust 第2版」p.178
コメント中にコードブロックを入れると自動的にテストされる
code:Rust
/// A block of code in a doc comment:
///
/// if samples::everything().works() {
/// println!("ok");
/// }
コメントのサンプルコードが上手く動く状態に保たれやすくていいかも
docテストの準備のための行
「プログラミングRust 第2版」p.180
コード片が動作するのに必要となるが、ドキュメントには出力したくない行は#を前置する
code:Rust
/// 太陽を光らせて、指定された時間だけ、シミュレーションを実行する
///
/// # use fern_sim::Terrarium;
/// # use std::time::Duration;
/// # let mut tm = Terrarium::new();
/// tm.apply_sunlight(Duration::from_secs(60));
///
pub fn apply_sunlight(&mut self, time: Duration) {
...
}
テラリウム
https://gyazo.com/8a35c38c104c3a1906fabcf031280bb0
?コードブロックの属性
「プログラミングRust 第2版」p.180
code:Rust
/// Upload all local terrariums to the online gallery.
///
/// `no_run
/// let mut session = fern_sim::connect();
/// session.upload_all();
/// `
pub fn upload_all(&mut self) {
...
}
ドキュメントのビルドのたびにアップロードされては困る
他に ignore、c++、sh、text など
行頭に4つの空白を入れる形式のコードブロックでは属性を付与する方法がない?
Semantic Versioning
「プログラミングRust 第2版」p.181
Rustが採用するバージョン番号のルール
code:バージョン指定と、互換性があるとされるバージョン範囲
1.2.3 := >=1.2.3, <2.0.0
1.2 := >=1.2.0, <2.0.0
1 := >=1.0.0, <2.0.0
0.2.3 := >=0.2.3, <0.3.0
0.2 := >=0.2.0, <0.3.0
0.0.3 := >=0.0.3, <0.0.4
0.0 := >=0.0.0, <0.1.0
0 := >=0.0.0, <1.0.0
バージョン番号には意味がある。クレートの管理者とユーザの間の契約なのだ。
バージョン1.7のクレートから関数を削除するのであれば、バージョン番号を2.0に上げざるを得ない。
バージョン指定のカスタマイズ
「プログラミングRust 第2版」p.182
table:バージョン指定方法
Cargo.tomlの記載例 挙動
=0.10.0 厳密に0.10.0
=1.0.5 1.0.5以上の任意のバージョン(2.9もアリ)
1.0.5 <1.1.9 1.0.5 < x < 1.1.9
<=2.7.10 2.7.10以下の任意のバージョン
Cargo.lock
「プログラミングRust 第2版」p.182
意図せずライブラリのバージョンが更新されることを防ぐ仕組み
デバッグ中に不意に最新化されたら困る
プロジェクトを最初にビルドした時点での正確なバージョンが記載される
Cargo.tomlを変更するか、cargo updateを実行したときだけ更新される
ワークスペース
「プログラミングRust 第2版」p.185
ビルド成果物を複数のクレート間で共有する仕組み
code:ディレクトリ構成
top/
.git/...
クレート1/
target/
クレート2/
target/
そのままではtargetがクレート間で共有されない
クレート1、2で同じ外部クレートを使っていても、それぞれビルドされる
code:top/Cargo.toml
サブディレクトリのCargo.lock、targetは削除しておく
自動でtop/targetが作成される