この資料について
この資料は「Rust Book 勉強会 #7」用の発表資料です。
- 担当: Yuya Kato (Nayutaya Inc.)
- 範囲: 第14章「More About Cargo and Crates.io」
- 原文: More about Cargo and Crates.io - The Rust Programming Language
- 日本語版: CargoとCrates.ioについてより詳しく
第14章のサマリ
第14章の各節では、以下のような内容が紹介されています。
- 「プロファイル」を使ってビルドをカスタマイズする方法
- crates.ioでパッケージを公開する方法
- ドキュメンテーションコメントを追加する方法
- 「ワークスペース」を使ってプロジェクトを整理する方法
- crates.ioから実行可能ファイルをインストールする方法
-
cargo
コマンドを拡張する方法
第14章で登場するコマンド
第14章では、以下のコマンド(とオプション)が登場します。(登場順)
-
cargo build
: ビルド-
cargo build --release
: リリースプロファイルを使ったビルド
-
-
cargo test
: テストの実行-
cargo test -p xxx
: 一部のテストの実行
-
-
cargo doc
: ドキュメントの生成-
cargo doc --open
: ドキュメントをブラウザで開く
-
-
cargo login xxx
: crates.ioへのログイン -
cargo publish
: パッケージの公開 -
cargo yank
: パッケージの取り下げ -
cargo new
: パッケージの作成-
cargo new xxx --lib
: ライブラリパッケージの作成
-
-
cargo run
: 実行-
cargo run -p xxx
: 指定したパッケージの実行
-
-
cargo install xxx
: パッケージのインストール -
cargo --list
: コマンド一覧の表示
1. プロファイルによるビルドのカスタマイズ
プロファイルとは?
Cargoでは「プロファイル」(profile)という機構によってさまざまなビルドオプションを制御することができます。
Cargoには、主なプロファイルとして以下の2つが存在します。
プロファイル | コマンド | 説明 |
---|---|---|
dev |
cargo build |
開発/デバッグ用 |
release |
cargo build --release |
リリース用 |
※補足: 他にもtest
、bench
が存在します。
プロファイル: 実行例
以下に実行例を示します。
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
$ cargo build --release
Finished release [optimized] target(s) in 0.0 secs
プロファイルの上書き (1/2)
Cargo.toml
に[profile.*]
というセクションを追加することでデフォルト値を上書き(カスタマイズ)することができます。例えばopt-level
は以下のデフォルト値を持ちます。
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
opt-level
は0
から3
までの整数を指定でき、数字が大きいほどより高度な最適化を行うことを示します。
高度な最適化には時間を要するため、頻繁にビルドを行う開発時は最適化を行わないようになっています。その代わり、生成されるオブジェクトコードは低速です。
リリース時には最適化を行い、時間は掛かりますが高速なオブジェクトコードを出力します。
プロファイルの上書き (2/2)
dev
プロファイルのopt-level
オプションを上書きする例を以下に示します。
[profile.dev]
opt-level = 1
上記のCargo.toml
が指定された状態でcargo build
を行うと、opt-level
に1
が設定された状態でビルドが行われます。
詳しいオプションやデフォルト値については「Cargoのドキュメント」を参照してください。
2. クレートをcrates.ioで公開する
クレートを公開する
ここまでに、crates.ioに存在するクレートをプロジェクトの依存関係に追加する方法を学んできました。本節では、クレートを自身で作成して、公開、共有する方法を学びましょう。
クレートを公開するまでのステップ
新しいクレートを公開する手順は以下の通りです。
- ドキュメンテーションコメントの追加
- テストの追加
- 公開APIを整理する
- crates.ioのアカウントをセットアップする
- メタデータを追加する
- crates.ioに公開する
また、既に公開しているクレートを更新、取り下げする方法についても学びましょう。
2.1. ドキュメンテーションコメント
ドキュメンテーションコメント
第3章では//
(2連スラッシュ)を使ってRustコードにコメントを追加する方法を学びました。
Rustには///
(3連スラッシュ)、//!
(2連スラッシュ+エクスクラメーションマーク)というコメントの表記もあり、これらは「ドキュメンテーションコメント」と呼ばれます。
これらのドキュメンテーションコメントからHTMLドキュメントが生成されます。
次ページに例を示します。
ドキュメンテーションコメント: 具体例
要素に対するドキュメンテーションコメントは///
で始まり、Markdown記法をサポートしています。ドキュメント対象の要素の直前に記述する必要があります。
以下は、my_crate
クレートのadd_one
関数のドキュメンテーションコメントの例です。
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let five = 5;
///
/// assert_eq!(6, my_crate::add_one(5));
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
Listing 14-1: A documentation comment for a function
Markdown記法についての補足:
-
#
、##
などで始まる行は見出しを示します。 -
```
で括られた行はコードを示します。
ドキュメントの生成例
cargo doc
コマンドを実行すると、ドキュメンテーションコメントからドキュメントを生成し、target/doc
に出力することができます。内部的にはrustdoc
が使用されています。
また、cargo doc --open
を実行すると、Webブラウザでドキュメントを閲覧することができます。生成されたドキュメントの例を以下に示します。
Figure 14-1: HTML documentation for the add_one function
よく使われるセクション
前述の例に含まれた「Examples」の他に、以下のセクションがよく用いられます。
- Panics: 関数が
panic!
する可能性のあるシナリオを記述します。パニックさせたくない関数の使用者は、これらの状況にならないように気を付ける必要があります。 - Errors: 関数が
Result
を返すのであれば、どんなエラーがどんな条件で発生するのかを記述します。 - Safety: 関数が
unsafe
ならその理由を説明し、期待する不変条件などを記述します。(unsafe
については第19章を参照)
2.2. テストとしてのドキュメンテーションコメント
テストとしてのドキュメンテーションコメント
先ほどの例のように、ドキュメンテーションコメントに「使用例」としてコードブロックを追加すると、説明文として意味があるだけでなく、cargo test
でテストすることができます。(いわゆる「doctest」)
「例付きのドキュメントに上回るものはありません。しかし、メンテナンスされていないがために動かなくなっている例よりも悪いものもありません。」
ドキュメンテーションコメントに実行可能な例を含めることで、例が動かなくなることを防ぐことができます。
以下に実行例を示します。
Doc-tests my_crate
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
クレートやモジュールにコメントする
///
ではなく//!
を用いることで、コメントの直後の要素に対してではなく、そのコメントが含まれる要素(クレート、モジュール全体)に対してコメントを付加することができます。具体例を以下に示します。
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
Listing 14-2: Documentation for the my_crate crate as a whole
生成されたドキュメントの例を次ページに示します。
ドキュメントの生成例
2.3. pub use
で便利な公開APIをエクスポートする
内部構造と公開API
第7章では:
-
mod
キーワードを使用してコードをモジュール化(構造化)する方法 -
pub
キーワードで要素を公開する方法 -
use
キーワードで要素をスコープに導入する方法
を学びました。しかし、クレートの内部構造は、クレートを使う側にとって便利ではない可能性があります。
例えばクレート内部の型が階層化されている場合、使う側がuse my_crate::some_module::another_module::UsefulType;
とするのは煩わしく感じます。
ここは単にuse my_crate::UsefulType;
と書けると便利です。本節ではその方法について学びましょう。
内部構造の例
例として以下のコードを用います。PrimaryColor
、SecondaryColor
という2つの列挙型を含むkinds
モジュールと、kinds
モジュールとmix
関数を含むutils
モジュールです。
//! # Art
//!
//! A library for modeling artistic concepts.
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
use kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
}
}
Listing 14-3: An art library with items organized into kinds and utils modules
この例から生成されたドキュメントの例を次ページに示します。
生成されたドキュメントの例
Figure 14-3: Front page of the documentation for art that lists the kinds and utils modules
画面の通り、PrimaryColor
型、SecondaryColor
型、mix
関数はトップページには現れず、参照するためにはkinds
、utils
をクリックする必要があります。
利用するクレートの例
前述のクレートを利用するクレートの例を以下に示します。
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
Listing 14-4: A crate using the art crate’s items with its internal structure exported
上記の通り、クレートの利用者はPrimaryColor
型がkinds
モジュールにあり、mix
関数がutils
モジュールにあることを特定する必要があります。art
クレートの内部構造は、クレートの利用者からすれば意味のある情報を含んでいません。
pub use
による再エクスポート
pub use
を用いることで、内部構造を除去して再エクスポートすることができます。例を以下に示します。
//! # Art
//!
//! A library for modeling artistic concepts.
pub use kinds::PrimaryColor;
pub use kinds::SecondaryColor;
pub use utils::mix;
pub mod kinds {
// --snip--
}
pub mod utils {
// --snip--
}
Listing 14-5: Adding pub use statements to re-export items
この例から生成されたドキュメントの例を次ページに示します。
生成されたドキュメントの例
Figure 14-4: The front page of the documentation for art that lists the re-exports
画面の通り、再エクスポートされた要素が列挙され、見つけやすくなります。
再エクスポートされた要素を利用する
再エクスポートされている場合、以前のように内部構造を利用することも、以下のようにより便利な構造を利用することもできます。
またクレートの開発者から見れば、内部構造と公開APIを切り離すことができます。
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
}
Listing 14-6: A program using the re-exported items from the art crate
2.4. アカウントのセットアップ
crates.ioのアカウントをセットアップする
クレートを公開するためにはcrates.ioのアカウントを作成する必要があります。また、crates.ioのアカウント作成にはGitHubアカウントが必要です。(いずれGitHubアカウントが無くてもログインできるようになる予定)
ログイン後、 https://crates.io/me にアクセスしてAPIキー(トークン)を取得してください。APIキーと共にcargo login
コマンドを実行すれば準備完了です。
$ cargo login abcdefghijklmnopqrstuvwxyz012345
上記のコマンドは、APIキーを~/.cargo/credentials
に保存します。APIキーは秘密にするべきであり、他人と共有してはなりません。何らかの理由によりAPIキーが漏れた場合は https://crates.io/me から再生成することができます。
2.5. メタデータの追加
メタデータを追加する (1/2)
クレートを公開する場合、いくつかのメタデータの追加が必要です。具体的にはCargo.toml
の[package]
セクションにデータを追加する必要があります。以下に例を示します。
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
各項目については次ページで説明します。
メタデータを追加する (2/2)
-
name
: クレートの名称です。crates.io全体でユニークである必要があり、早い者勝ちです。 -
version
: バージョン番号です。セマンティックバージョンルールに基づいて付加するのが望ましいです。 -
description
: クレートの説明文です。 -
license
: クレートのライセンスです。OR
で区切ることで、複数のライセンスの選択肢を示すことができます。RustコミュニティではMIT OR Apache-2.0
が多く用いられます。
その他の項目についてはCargoのドキュメントを参照してください。
2.6. 公開と更新、取り下げ
crates.ioに公開する
アカウントを作成し、APIキーを登録し、メタデータを追加したので、クレートを公開する準備ができました。
実際に公開する前に注意点。公開は恒久的です。バージョンを上書きすることも、コードを削除することもできません。これはcrates.ioが、クレートに依存しているすべてのプロジェクトがいつでもビルドできるように永久アーカイブとして機能しているためです。
cargo publish
コマンドを実行すると、クレートを公開することができます。実行例を以下に示します。
$ cargo publish
Updating registry `https://github.com/rust-lang/crates.io-index`
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished dev [unoptimized + debuginfo] target(s) in 0.19 secs
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
公開済みのクレートの新バージョンを公開する(アップデートする)
クレートのコードを更新し、新バージョンをリリースする準備ができたら、Cargo.toml
ファイルのversion
を更新し、cargo publish
コマンドを実行してください。
バージョン番号は、セマンティックバージョンルールに基づき、加えた変更の種類に応じて適切な値を決定します。
特定のバージョンを取り下げる
クレートの特定のバージョンを削除することはできませんが、「取り下げ」を行い、新たに依存関係として追加されることを防ぐことはできます。
バージョンを取り下げても、引きつづきダウンロードし、利用することはできます。Cargo.lock
で指定されている限り、プロジェクトが壊れることはありません。
cargo yank
コマンドで特定のバージョン取り下げを行うことができます。
$ cargo yank --vers 1.0.1
また、--undo
オプションで取り下げの取り消しを行うことができます。
$ cargo yank --vers 1.0.1 --undo
繰り返しになりますが、コードを削除することはできません。できるのは、新たに依存クレートとして加えられることを防ぐことだけです。
3. Cargoのワークスペース
Cargoのワークスペース
本節では、以下の内容について学びましょう。
- ワークスペースを生成する
- ワークスペース内にクレートを追加する
- ワークスペースに外部クレートを追加する
- ワークスペースにテストを追加する
第12章では、バイナリクレートとライブラリクレートを含むパッケージを構築しました。プロジェクトの開発が進むにつれてクレートが肥大化し、パッケージを分割したくなることでしょう。
そんな場面において便利なのが「ワークスペース」です。
ワークスペースを生成する (1/3)
ワークスペースは、同じCargo.lock
と出力ディレクトリを共有する一連のパッケージです。
例として、バイナリ1つとライブラリ2つを含むワークスペースを作ります。バイナリは主要な機能を提供しますが、ライブラリに依存しています。1つ目のライブラリはadd_one
関数を提供し、2つ目のライブラリはadd_two
関数を提供します。これら3つのクレートがワークスペースの一部となります。
まずはワークスペース用のディレクトリを作りましょう。
$ mkdir add
$ cd add
ワークスペースを生成する (2/3)
続いて、ワークスペース全体の設定を行うCargo.toml
を作成します。
[workspace]
members = [
"adder",
]
このCargo.toml
はとてもシンプルで、[package]
セクションやメタデータを含みません。その代わり、ワークスペースのメンバを管理する[workspace]
セクションを含みます。
ワークスペースを生成する (3/3)
続いて、add
ディレクトリ内でadder
クレートを作成しましょう。
$ cargo new adder
Created binary (application) `adder` project
この時点でcargo build
を事項すると、ワークスペースをビルドできます。現状の構成は以下の通りです。
add
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
ワークスペース内にクレートを追加する (1/5)
次に、add-one
ライブラリクレートを追加しましょう。ワークスペースのCargo.toml
を以下のように変更します。
[workspace]
members = [
"adder",
"add-one",
]
ワークスペース内にクレートを追加する (2/5)
それから、ライブラリクレートを作成します。
$ cargo new add-one --lib
Created library `add-one` project
現状の構成は以下の通りです。
add
├── Cargo.lock
├── Cargo.toml
├── add-one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
ワークスペース内にクレートを追加する (3/5)
次に、add_one
関数を追加しましょう。
pub fn add_one(x: i32) -> i32 {
x + 1
}
この関数を利用するため、adder
バイナリクレートに依存関係を追加しましょう。
[dependencies]
add-one = { path = "../add-one" }
ワークスペース内にクレートを追加する (4/5)
次に、adder
バイナリクレートに、実際にadd_one
を呼ぶ処理を追加します。
use add_one;
fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}
Listing 14-7: Using the add-one library crate from the adder crate
ワークスペース内にクレートを追加する (5/5)
呼び出し処理を追加したので、ビルド、実行してみましょう。
$ cargo build
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
無事にadd-one
クレートに依存した処理が実行されました。
外部クレートをワークスペースに追加する (1/3)
ワークスペース内のそれぞれのクレートにはCargo.lock
が存在せず、ワークスペースの最上位ディレクトリにただ1つのCargo.lock
が存在します。
これにより、すべてのクレートが同一バージョンのクレートを使用します。
rand
クレートをadder/Cargo.toml
(バイナリクレート)とadd-one/Cargo.toml
(ライブラリクレート)に追加すると、Cargoはバージョンの解決を行い、トップディレクトリのCargo.lock
に記録します。
外部クレートをワークスペースに追加する (2/3)
add-one
クレートにrand
クレートを追加してみましょう。
[dependencies]
rand = "0.3.14"
続いてadd-one/src/lib.rs
にextern crate rand;
を追加し、add
ディレクトリ(ワークスペース)でcargo build
を実行するとrand
の取得が行われます。
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.14
--snip--
Compiling rand v0.3.14
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
外部クレートをワークスペースに追加する (3/3)
トップディレクトリのCargo.lock
にrand
に対する依存が記録されている状態になりましたが、ワークスペース内のそれぞれのクレートのCargo.toml
にrand
を追加しない限り、それぞれのクレートで利用することはできません。
例えば、adder/src/main.rs
にextern crate rand;
を追加すると、以下のエラーが出力されます。
$ cargo build
Compiling adder v0.1.0 (file:///projects/add/adder)
error: use of unstable library feature 'rand': use `rand` from crates.io (see
issue #27703)
--> adder/src/main.rs:1:1
|
1 | extern use rand;
ワークスペースにテストを追加する (1/3)
さらなる改善としてadd-one
クレートにadd_one::add_one
関数のテストを追加しましょう。
pub fn add_one(x: i32) -> i32 {
x + 1
}
# [cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
ワークスペースにテストを追加する (2/3)
トップディレクトリでcargo test
を実行すると、すべてのクレートのテストが実行されます。
$ cargo test
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running target/debug/deps/add_one-f0253159197f7841
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/adder-f88af9d2cc175a5e
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ワークスペースにテストを追加する (3/3)
一部のテストのみ実行したい場合、cargo test -p xxx
を使うことができます。
$ cargo test -p add-one
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running target/debug/deps/add_one-b3235fea9a156f74
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ワークスペースに含まれるクレートの公開
ワークスペースのそれぞれのクレートは、個別にcrates.ioに公開する必要があります。
つまり、それぞれのクレートのディレクトリでcargo publish
コマンドを実行する必要があります。残念ながら--all
のようなオプションはありません。
ワークスペース: 練習問題とヒント
練習問題: add-one
クレートと同様に、add-two
クレートをワークスペースに追加してください。
ヒント: プロジェクトが肥大化してきたら、ワークスペースの使用を考えましょう。一つの大きな固まりより、小さく、個別のコンポーネントの方が理解が容易です。また、複数人での共同作業も容易になります。
4. crates.ioからバイナリをインストールする
cargo install
: バイナリのインストール
cargo install
コマンドを使うことで、バイナリクレート(実行可能ファイル)をローカルにインストールし、使用することができます。
ただこれは、OSが持つようなパッケージ管理システムを置き換えることを意図したものではありません。
cargo install
コマンドでは、バイナリターゲットを持つパッケージのみをインストールできることに注意してください。単独では実行できない、ライブラリターゲットをインストールすることはできません。
cargo install
コマンドでインストールされるバイナリは、rustup
を使用し、かつ独自の設定を行っていなければ$HOME/.cargo/bin
に格納されます。このディレクトリが$PATH
に含まれている必要があります。
具体例: ripgrep
のインストール
第12章で触れたripgrep
をインストールしてみましょう。
$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading ripgrep v0.3.2
--snip--
Compiling ripgrep v0.3.2
Finished release [optimized + debuginfo] target(s) in 97.91 secs
Installing ~/.cargo/bin/rg
メッセージにある通り、~/.cargo/bin/rg
にインストールされました。rg --help
を実行して、ツールが動作することを確認しましょう。
5. cargo
コマンドを拡張する方法
独自のサブコマンドでcargo
を拡張する
Cargoは、それ自体を変更することなく、新しいサブコマンドで拡張できるように設計されています。
$PATH
に存在するバイナリがcargo-something
という名前なら、cargo something
を実行することで、サブコマンドであるかのように実行することができます。
このような独自コマンドは、cargo --list
コマンドでも列挙されます。
cargo install
コマンドを使って拡張をインストールし、組み込みのCargoサブコマンドと同様に実行できることは、Cargoの設計上の優れた点です。
6. まとめ
まとめ
- Cargoとcrates.ioを使ってコードを共有することは、Rustのエコシステムの有用な一部です。
- Rustの標準ライブラリは小さく安定的ですが、クレートは共有も使用も容易で、言語とは異なるタイムラインで進化します。
- 積極的にcrates.ioでコードを共有し、みんなの役に立ちましょう!