はじめに
Publishing a Crate to Crates.io へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習することができます。
この実験では、オープンソースコードを配布するクレートレジストリである crates.io にクレートを公開する方法について説明します。これにより、人々があなたのパッケージを見つけて使用しやすくなります。
Crates.io にクレートを公開する
私たちは、https://crates.ioからのパッケージをプロジェクトの依存関係として使用してきましたが、自分自身のパッケージを公開することで、他の人とコードを共有することもできます。https://crates.ioのクレートレジストリは、あなたのパッケージのソースコードを配布するため、主にオープンソースのコードをホストしています。
Rust と Cargo には、公開されたパッケージを人々が見つけやすく利用しやすくする機能があります。次にこれらの機能の一部について説明し、その後、パッケージを公開する方法を説明します。
役立つドキュメントコメントの作成
あなたのパッケージを正確に文書化することは、他のユーザーがそれをどのように、いつ使用するかを知るのに役立ちますので、文書を書くための時間を投資する価値があります。第 3 章では、2 つのスラッシュ // を使って Rust コードにコメントを付ける方法について説明しました。Rust には、HTML ドキュメントを生成する、便利にも ドキュメントコメント と呼ばれる特定の種類のコメントもあります。この HTML は、あなたのクレートがどのように 実装 されているかではなく、どのように 使用 するかを知りたいプログラマーに向けたパブリック API 項目のドキュメントコメントの内容を表示します。
ドキュメントコメントでは、2 つの代わりに 3 つのスラッシュ /// を使用し、Markdown 表記をサポートしてテキストをフォーマットします。ドキュメントコメントは、それが文書化する項目の直前に配置します。リスト 14-1 は、my_crate というクレート内の add_one 関数のドキュメントコメントを示しています。
ファイル名:src/lib.rs
/// Adds one to the number given.
///
/// ## Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```rust
pub fn add_one(x: i32) -> i32 {
x + 1
}
リスト 14-1: 関数のドキュメントコメント
ここでは、add_one 関数が何をするかの説明を与え、Examples という見出しのセクションを始め、その後、add_one 関数をどのように使用するかを示すコードを提供しています。このドキュメントコメントから HTML ドキュメントを生成するには、cargo doc を実行します。このコマンドは、Rust に付属する rustdoc ツールを実行し、生成された HTML ドキュメントを target/doc ディレクトリに置きます。
便利なことに、cargo doc --open を実行すると、現在のクレートのドキュメント(およびクレートのすべての依存関係のドキュメント)の HTML を生成し、結果をウェブブラウザで開きます。add_one 関数に移動すると、ドキュメントコメントのテキストがどのようにレンダリングされるかを確認できます。図 14-1 を参照してください。
図 14-1: add_one 関数の HTML ドキュメント
よく使われるセクション
リスト 14-1 では、## Examples という Markdown 見出しを使って、HTML 内に「Examples」というタイトルのセクションを作成しました。クレートの作者がドキュメントに一般的に使用する他のセクションをいくつか紹介します。
- パニック: 文書化されている関数がパニックする可能性のあるシナリオ。プログラムがパニックしないようにする関数の呼び出し元は、これらの状況で関数を呼び出さないようにする必要があります。
- エラー: 関数が
Resultを返す場合、発生する可能性のあるエラーの種類と、それらのエラーが返される条件を説明することは、呼び出し元にとって役立ちます。それにより、異なる種類のエラーを異なる方法で処理するコードを書くことができます。 - セーフティ: 関数を呼び出すのが
unsafeである場合(第 19 章で非セーフティについて説明します)、関数が非セーフな理由と、関数が呼び出し元に維持することを期待する不変条件を説明するセクションが必要です。
ほとんどのドキュメントコメントにはこれらすべてのセクションが必要ではありませんが、これは、コードのユーザーが知りたいと思う側面を思い出させるための良いチェックリストです。
テストとしてのドキュメントコメント
ドキュメントコメントにサンプルコードブロックを追加することで、ライブラリの使い方を示すことができます。そして、これには追加の特典があります。cargo test を実行すると、ドキュメント内のコード例がテストとして実行されます!例を含んだドキュメントより良いものはありません。しかし、ドキュメント作成以降にコードが変更されたために動作しない例ほど悪いものはありません。リスト 14-1 の add_one 関数のドキュメントを使って cargo test を実行すると、テスト結果に次のようなセクションが表示されます。
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; finished in 0.27s
次に、関数または例を変更して、例の中の assert_eq! がパニックするようにして、再度 cargo test を実行すると、ドキュメントテストが例とコードが同期していないことを検出することがわかります!
含まれる項目のコメント付け
ドキュメントコメント //! は、コメントを 含む 項目にドキュメントを追加し、コメントの 後に続く 項目には追加しません。通常、これらのドキュメントコメントをクレートのルートファイル(慣例では src/lib.rs)内またはモジュール内に使用して、クレート全体またはモジュールを文書化します。
たとえば、add_one 関数を含む my_crate クレートの目的を説明するドキュメントを追加するには、src/lib.rs ファイルの先頭に //! で始まるドキュメントコメントを追加します。リスト 14-2 を参照してください。
ファイル名: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--
リスト 14-2: my_crate クレート全体のドキュメント
//! で始まる最後の行の後にコードがないことに注意してください。コメントを //! で始めた代わりに /// で始めたので、このコメントを含む項目、つまりこのコメントの後に続く項目ではなく、このコメントを含む項目を文書化しています。この場合、その項目はクレートのルートである src/lib.rs ファイルです。これらのコメントはクレート全体を説明します。
cargo doc --open を実行すると、これらのコメントが、クレート内のパブリック項目の一覧の上にある my_crate のドキュメントの最初のページに表示されます。図 14-2 を参照してください。
図 14-2: my_crate のレンダリングされたドキュメント。クレート全体を説明するコメントも含まれています。
項目内のドキュメントコメントは、クレートとモジュールを説明するのに特に役立ちます。コンテナの全体的な目的を説明するために使用して、ユーザーがクレートの構成を理解するのを助けてください。
pub use を使った便利なパブリック API のエクスポート
クレートを公開する際には、パブリック API の構造が重要な考慮事項です。クレートを使用する人は、あなたほど API の構造に慣れておらず、大きなモジュール階層を持つクレートの場合、使いたい部分を見つけるのに苦労するかもしれません。
第 7 章では、pub キーワードを使って項目を公開する方法と、use キーワードを使って項目をスコープに持ち込む方法について説明しました。しかし、クレートを開発している最中には分かりやすい構造であっても、ユーザーにとっては必ずしも便利ではない場合があります。たとえば、構造体を多段階の階層に組織化することが考えられますが、その場合、階層の奥に定義された型を使おうとする人は、その型が存在することを知るのに苦労するかもしれません。また、use my_crate::some_module::another_module::UsefulType; と入力するのが面倒くさく、use my_crate::UsefulType; のように書きたいと思うかもしれません。
幸いなことに、他のライブラリから使いにくい構造の場合、内部組織を再配置する必要はありません。代わりに、pub use を使って項目を再エクスポートして、内部構造とは異なるパブリック構造を作成することができます。再エクスポート は、ある場所のパブリック項目を取り出して、別の場所で公開するもので、その項目が別の場所に定義されているかのように見えます。
たとえば、アート的な概念をモデリングするための art という名前のライブラリを作ったとします。このライブラリには 2 つのモジュールがあります。kinds モジュールには PrimaryColor と SecondaryColor という 2 つの列挙型が含まれ、utils モジュールには mix という名前の関数が含まれています。リスト 14-3 を参照してください。
ファイル名: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 crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(
c1: PrimaryColor,
c2: PrimaryColor,
) -> SecondaryColor {
--snip--
}
}
リスト 14-3: kinds と utils モジュールに項目を組織化した art ライブラリ
図 14-3 は、cargo doc によって生成されるこのクレートのドキュメントの最初のページの様子を示しています。
図 14-3: kinds と utils モジュールをリストする art のドキュメントの最初のページ
PrimaryColor と SecondaryColor 型は最初のページに表示されず、mix 関数も表示されません。それらを見るには、kinds と utils をクリックする必要があります。
このライブラリに依存する別のクレートでは、art からの項目をスコープに持ち込むための use 文が必要で、現在定義されているモジュール構造を指定する必要があります。リスト 14-4 は、art クレートの PrimaryColor と mix 項目を使うクレートの例を示しています。
ファイル名:src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
リスト 14-4: 内部構造をエクスポートした art クレートの項目を使うクレート
リスト 14-4 のコードの作者は、PrimaryColor が kinds モジュールにあり、mix が utils モジュールにあることを把握する必要がありました。art クレートのモジュール構造は、art クレートを開発する開発者にとってはより関連性が高いものの、それを使用する人にとっては有用な情報を含んでいません。むしろ混乱を招きます。なぜなら、使用する開発者はどこを見るべきかを判断しなければならず、use 文でモジュール名を指定する必要があるからです。
パブリック API から内部組織を取り除くには、リスト 14-3 の art クレートのコードを変更して、pub use 文を追加して項目をトップレベルで再エクスポートします。リスト 14-5 を参照してください。
ファイル名:src/lib.rs
//! ## Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
--snip--
}
pub mod utils {
--snip--
}
リスト 14-5: 項目を再エクスポートするための pub use 文の追加
このクレートに対して cargo doc が生成する API ドキュメントは、現在、最初のページに再エクスポートをリストしてリンク付けします。図 14-4 を参照してください。これにより、PrimaryColor と SecondaryColor 型および mix 関数を見つけやすくなります。
図 14-4: 再エクスポートをリストする art のドキュメントの最初のページ
art クレートのユーザーは、リスト 14-4 に示すように、リスト 14-3 の内部構造を依然として見ることができ、使用することもできます。または、リスト 14-6 に示すように、リスト 14-5 のより便利な構造を使用することもできます。
ファイル名:src/main.rs
use art::mix;
use art::PrimaryColor;
fn main() {
--snip--
}
リスト 14-6: art クレートから再エクスポートされた項目を使用するプログラム
多くのネストされたモジュールがある場合、pub use を使ってトップレベルで型を再エクスポートすることは、クレートを使用する人の経験に大きな違いをもたらすことができます。pub use のもう 1 つの一般的な使い方は、現在のクレートにおける依存関係の定義を再エクスポートして、そのクレートの定義を自分のクレートのパブリック API の一部にすることです。
便利なパブリック API 構造を作成することは、科学よりも芸術に近いものであり、ユーザーに最適な API を見つけるために反復することができます。pub use を選択することで、クレートの内部構造をどのように構築するかに柔軟性が与えられ、その内部構造とユーザーに提示するものを切り離すことができます。インストールしたクレートのコードを見て、その内部構造とパブリック API が異なるかどうかを確認してみてください。
Crates.io アカウントの設定
クレートを公開する前に、https://crates.io でアカウントを作成し、API トークンを取得する必要があります。そのためには、https://crates.io のホームページにアクセスし、GitHub アカウントでログインします。(現在は GitHub アカウントが必要ですが、将来的には他のアカウント作成方法をサポートする可能性があります。)ログイン後、https://crates.io/me のアカウント設定にアクセスして API キーを取得します。そして、次のように API キーを使って cargo login コマンドを実行します。
cargo login abcdefghijklmnopqrstuvwxyz012345
このコマンドにより、Cargo に API トークンを通知し、それを ~/.cargo/credentials にローカルに保存します。このトークンは 秘密情報 であることに注意してください。他の誰かと共有しないでください。何らかの理由で誰かと共有した場合は、それを取り消し、https://crates.io で新しいトークンを生成する必要があります。
新しいクレートにメタデータを追加する
公開したいクレートがあるとしましょう。公開する前に、クレートの Cargo.toml ファイルの [package] セクションにいくつかのメタデータを追加する必要があります。
クレートには一意の名前が必要です。ローカルでクレートを開発している間は、好きな名前を付けることができます。ただし、https://crates.io 上のクレート名は、先着順で割り当てられます。クレート名が既に使われている場合、他の誰もその名前でクレートを公開することはできません。クレートを公開しようとする前に、使用したい名前を検索してください。名前が既に使われている場合は、別の名前を見つけて、[package] セクションの下の Cargo.toml ファイルの name フィールドを編集して、公開に使用する新しい名前に変更します。次のようになります。
ファイル名:Cargo.toml
[package]
name = "guessing_game"
一意の名前を選んだ場合でも、この時点で cargo publish を実行してクレートを公開すると、警告が表示され、その後エラーが表示されます。
$ cargo publish
Updating crates.io index
warning: manifest has no description, license, license-file, documentation,
homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata
for more info.
--snip--
error: failed to publish to registry at https://crates.io
Caused by:
the remote server responded with an error: missing or empty metadata fields:
description, license. Please see https://doc.rust-
lang.org/cargo/reference/manifest.html for how to upload metadata
これがエラーになるのは、重要な情報が不足しているためです。クレートが何を行うか、そしてどの条件で使用できるかを知るためには、説明とライセンスが必要です。Cargo.toml では、検索結果にクレートと一緒に表示されるため、1、2 文の説明を追加します。license フィールドでは、ライセンス識別子値 を指定する必要があります。http://spdx.org/licenses の Linux Foundation の Software Package Data Exchange (SPDX) では、この値に使用できる識別子が一覧されています。たとえば、クレートを MIT ライセンスでライセンス付けしたことを指定するには、MIT 識別子を追加します。
ファイル名:Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
SPDX に表示されていないライセンスを使用したい場合は、そのライセンスのテキストをファイルに置き、プロジェクトにそのファイルを含め、その後 license-file を使用してそのファイル名を指定して、license キーを使用しないようにします。
あなたのプロジェクトに適したライセンスに関するガイダンスは、この本の範囲外です。Rust コミュニティの多くの人は、MIT OR Apache-2.0 のデュアルライセンスを使用することで、Rust と同じように彼らのプロジェクトをライセンス付けています。この慣行は、プロジェクトに複数のライセンスを持たせるために、OR で区切られた複数のライセンス識別子を指定することもできることを示しています。
一意の名前、バージョン、説明、およびライセンスが追加された状態で、公開準備ができているプロジェクトの Cargo.toml ファイルは、次のようになるかもしれません。
ファイル名:Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess what number the
computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
https://doc.rust-lang.org/cargo の Cargo のドキュメントでは、他の人があなたのクレートをより簡単に見つけて使用できるようにするために指定できる他のメタデータについて説明されています。
Crates.io に公開する
アカウントを作成し、API トークンを保存し、クレートの名前を選び、必要なメタデータを指定したので、公開の準備が整いました!クレートを公開すると、特定のバージョンが https://crates.io にアップロードされ、他の人が使用できるようになります。
注意してください。公開は 恒久的 です。バージョンは決して上書きできず、コードを削除することもできません。Crates.io の主な目的の 1 つは、コードの恒久的なアーカイブとして機能することです。そうすることで、https://crates.io のクレートに依存するすべてのプロジェクトのビルドが継続して機能するようになります。バージョンの削除を許可すると、その目的を達成することが不可能になります。ただし、公開できるクレートのバージョン数に制限はありません。
再度 cargo publish コマンドを実行します。今回は成功するはずです。
$ cargo publish
Updating 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.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
おめでとうございます!あなたは今、あなたのコードを Rust コミュニティと共有しました。そして、誰でも簡単にあなたのクレートを自分のプロジェクトの依存関係として追加できます。
既存のクレートの新しいバージョンを公開する
クレートに変更を加え、新しいバージョンをリリースする準備ができたら、Cargo.toml ファイルで指定されている version 値を変更して再公開します。http://semver.org のセマンティック バージョニング規則を使用して、行った変更の種類に基づいて、次に適切なバージョン番号を決定します。そして、新しいバージョンをアップロードするために cargo publish を実行します。
cargo yank を使って Crates.io からバージョンを非推奨にする
クレートの以前のバージョンを削除することはできませんが、将来のプロジェクトがそれらを新しい依存関係として追加するのを防ぐことができます。これは、クレートのバージョンが何らかの理由で破損している場合に便利です。そのような状況では、Cargo はクレートのバージョンを引っ張ることをサポートしています。
バージョンを 引っ張る と、新しいプロジェクトがそのバージョンに依存することを防ぎますが、それに依存している既存のすべてのプロジェクトが継続することを許可します。基本的に、引っ張ることは、Cargo.lock を持つすべてのプロジェクトが壊れないことを意味し、生成される将来の Cargo.lock ファイルは引っ張られたバージョンを使用しなくなります。
クレートのバージョンを引っ張るには、以前に公開したクレートのディレクトリで cargo yank を実行し、引っ張りたいバージョンを指定します。たとえば、guessing_game という名前のクレートのバージョン 1.0.1 を公開し、それを引っ張りたい場合、guessing_game のプロジェクトディレクトリで次のコマンドを実行します。
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
コマンドに --undo を追加することで、引っ張りを元に戻して、プロジェクトが再びバージョンに依存できるようにすることもできます。
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
引っ張ることは、コードを削除しません。たとえば、誤ってアップロードされたシークレットを削除することはできません。そのようなことが起こった場合、それらのシークレットを直ちにリセットする必要があります。
まとめ
おめでとうございます!Crates.io にクレートを公開する実験を完了しました。LabEx でさらに実験を行って、スキルを向上させることができます。