Edited at

Rust Book 勉強会 #7 第14章「More About Cargo and Crates.io」


この資料について

この資料は「Rust Book 勉強会 #7」用の発表資料です。



第14章のサマリ

第14章の各節では、以下のような内容が紹介されています。


  1. 「プロファイル」を使ってビルドをカスタマイズする方法

  2. crates.ioでパッケージを公開する方法


    • ドキュメンテーションコメントを追加する方法



  3. 「ワークスペース」を使ってプロジェクトを整理する方法

  4. crates.ioから実行可能ファイルをインストールする方法


  5. 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
リリース用

※補足: 他にもtestbenchが存在します。



プロファイル: 実行例

以下に実行例を示します。

$ 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は以下のデフォルト値を持ちます。


Cargo.toml

[profile.dev]

opt-level = 0

[profile.release]
opt-level = 3


opt-level0から3までの整数を指定でき、数字が大きいほどより高度な最適化を行うことを示します。

高度な最適化には時間を要するため、頻繁にビルドを行う開発時は最適化を行わないようになっています。その代わり、生成されるオブジェクトコードは低速です。

リリース時には最適化を行い、時間は掛かりますが高速なオブジェクトコードを出力します。



プロファイルの上書き (2/2)

devプロファイルのopt-levelオプションを上書きする例を以下に示します。


Cargo.toml

[profile.dev]

opt-level = 1

上記のCargo.tomlが指定された状態でcargo buildを行うと、opt-level1が設定された状態でビルドが行われます。

詳しいオプションやデフォルト値については「Cargoのドキュメント」を参照してください。



2. クレートをcrates.ioで公開する



クレートを公開する

ここまでに、crates.ioに存在するクレートをプロジェクトの依存関係に追加する方法を学んできました。本節では、クレートを自身で作成して、公開、共有する方法を学びましょう。



クレートを公開するまでのステップ

新しいクレートを公開する手順は以下の通りです。


  1. ドキュメンテーションコメントの追加

  2. テストの追加

  3. 公開APIを整理する

  4. crates.ioのアカウントをセットアップする

  5. メタデータを追加する

  6. crates.ioに公開する

また、既に公開しているクレートを更新、取り下げする方法についても学びましょう。



2.1. ドキュメンテーションコメント



ドキュメンテーションコメント

第3章では//(2連スラッシュ)を使ってRustコードにコメントを追加する方法を学びました。

Rustには///(3連スラッシュ)、//!(2連スラッシュ+エクスクラメーションマーク)というコメントの表記もあり、これらは「ドキュメンテーションコメント」と呼ばれます。

これらのドキュメンテーションコメントからHTMLドキュメントが生成されます。

次ページに例を示します。



ドキュメンテーションコメント: 具体例

要素に対するドキュメンテーションコメントは///で始まり、Markdown記法をサポートしています。ドキュメント対象の要素の直前に記述する必要があります。

以下は、my_crateクレートのadd_one関数のドキュメンテーションコメントの例です。


src/lib.rs

/// 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ブラウザでドキュメントを閲覧することができます。生成されたドキュメントの例を以下に示します。

trpl14-01.png

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



クレートやモジュールにコメントする

///ではなく//!を用いることで、コメントの直後の要素に対してではなく、そのコメントが含まれる要素(クレート、モジュール全体)に対してコメントを付加することができます。具体例を以下に示します。


src/lib.rs

//! # 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

生成されたドキュメントの例を次ページに示します。



ドキュメントの生成例

trpl14-02.png



2.3. pub useで便利な公開APIをエクスポートする



内部構造と公開API

第7章では:



  • modキーワードを使用してコードをモジュール化(構造化)する方法


  • pubキーワードで要素を公開する方法


  • useキーワードで要素をスコープに導入する方法

を学びました。しかし、クレートの内部構造は、クレートを使う側にとって便利ではない可能性があります。

例えばクレート内部の型が階層化されている場合、使う側がuse my_crate::some_module::another_module::UsefulType;とするのは煩わしく感じます。

ここは単にuse my_crate::UsefulType;と書けると便利です。本節ではその方法について学びましょう。



内部構造の例

例として以下のコードを用います。PrimaryColorSecondaryColorという2つの列挙型を含むkindsモジュールと、kindsモジュールとmix関数を含むutilsモジュールです。


src/lib.rs

//! # 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

この例から生成されたドキュメントの例を次ページに示します。



生成されたドキュメントの例

trpl14-03.png

Figure 14-3: Front page of the documentation for art that lists the kinds and utils modules

画面の通り、PrimaryColor型、SecondaryColor型、mix関数はトップページには現れず、参照するためにはkindsutilsをクリックする必要があります。



利用するクレートの例

前述のクレートを利用するクレートの例を以下に示します。

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

この例から生成されたドキュメントの例を次ページに示します。



生成されたドキュメントの例

trpl14-04.png

Figure 14-4: The front page of the documentation for art that lists the re-exports

画面の通り、再エクスポートされた要素が列挙され、見つけやすくなります。



再エクスポートされた要素を利用する

再エクスポートされている場合、以前のように内部構造を利用することも、以下のようにより便利な構造を利用することもできます。

またクレートの開発者から見れば、内部構造と公開APIを切り離すことができます。


src/main.rs

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]セクションにデータを追加する必要があります。以下に例を示します。


Cargo.toml

[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を作成します。


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を以下のように変更します。


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関数を追加しましょう。


add-one/src/lib.rs

pub fn add_one(x: i32) -> i32 {

x + 1
}

この関数を利用するため、adderバイナリクレートに依存関係を追加しましょう。


adder/Cargo.toml

[dependencies]

add-one = { path = "../add-one" }




ワークスペース内にクレートを追加する (4/5)

次に、adderバイナリクレートに、実際にadd_oneを呼ぶ処理を追加します。


adder/src/main.rs

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クレートを追加してみましょう。


add-one/Cargo.toml

[dependencies]

rand = "0.3.14"


続いてadd-one/src/lib.rsextern 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.lockrandに対する依存が記録されている状態になりましたが、ワークスペース内のそれぞれのクレートのCargo.tomlrandを追加しない限り、それぞれのクレートで利用することはできません。

例えば、adder/src/main.rsextern 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関数のテストを追加しましょう。


add-one/src/lib.rs

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でコードを共有し、みんなの役に立ちましょう!