Rustのドキュメンテーションコメント(crateや関数などに対するコメントの書き方とドキュメント変換など)に関して備忘録として記事にしておきます。
ドキュメンテーションコメント(Doc comments)とは
ドキュメンテーションコメントとはRustdocを使用した際にドキュメントにコンパイルされるコメントのことです。///によって普通のコメントと区別され、ここではMarkdownを使用することができます。ドキュメンテーションコメントは大規模なプロジェクトの際に非常に有用です。
ドキュメンテーション - Rust By Example 日本語版より引用。
いわゆるJavaScriptでいうところのJSDoc、PythonでいうところのdocstringなどのRust版に該当します。
書くと主にどんなメリットがあるのか
- エディタ上で参照した際にこのコメントを参照できて可読性が上がります。
- ドキュメントとしてHTMLファイルに出力できます(Sphinxなどの外部ライブラリ無しに、Rustビルトインで対応されています)。
- ドキュメンテーションコメント内のサンプルコードなどに対してテストがRustのビルトインの機能だけでできるので、コメント内のサンプルコードがバージョンアップなどで動かなくなっている・・・といったことを避けることができます。
使う環境と準備
本記事では以下のRustの各バージョンを利用しています。
$ cargo --version
cargo 1.48.0 (65cbdd2dc 2020-10-14)
$ rustc --version
rustc 1.48.0 (7eac88abb 2020-11-16)
$ rustdoc --version
rustdoc 1.48.0 (7eac88abb 2020-11-16)
以降ではサンプルとしてdcocommentというバイナリクレートのプロジェクトを作って進めていきます。
$ cargo new doccomment
$ cd doccomment
プロジェクト自体にサードパーティーのライブラリは今回は利用しません。
エディタはVS Code、Rust用の拡張機能として以下のものを使っています。必要に応じてVS Code上で検索してご利用ください。
- Rust
- Tabnine (Pro版)
ファイル自体やモジュールへの基本的なコメントの書き方
Rustの.rs
ファイル自体(ファイルの最上部部分)へコメントを書きたい場合は//!
という記号を付与して書きます。Rustの通常のコメントが//
なので!
記号が増えた形となります。
//! ドキュメンテーションコメントの説明用に作ったサンプルの
//! プロジェクトです。
fn main() {
println!("Hello, world!");
}
上記のコメントは関数(例えばmainなど)へのコメントでは無いという点には注意してください(あくまでファイル全体に対するコメント)。
また、モジュールに対してもこのコメントの書き方は適用できます。例えば以下のようにモジュール定義の直下などに記述できます。
pub mod sample_module {
//! 整数値を取得するインターフェイスを定義したサンプルの
//! モジュールです。
pub fn get_two() -> i32 {
2
}
pub fn get_three() -> i32 {
3
}
}
ドキュメントファイルに出力する
cargoのコマンドでドキュメンテーションコメントの内容を加味した状態でドキュメントファイルに出力することができます。
以下のcargoのコマンドで出力することができます。
$ cargo doc
出力先は<プロジェクトディレクトリ>/target/doc/
以下に各ファイルが出力されます。
--open
引数を付与すると、ドキュメントを出力すると共にブラウザでドキュメントのルートページを開いてくれます。便利なので普段使いにはこの引数を指定することが多くなるのではと思います。
$ cargo doc --open
以下のようなページとなります。
//!
の記号を使ってファイルやモジュールに付与したドキュメンテーションコメントが反映されていることが確認できます。
筆のアイコンをクリックするとテーマを変更できます。黒いテーマが好きなので今回はayuというテーマを選択しました。
テーマ設定などは再度ドキュメントをコマンドで出力しても維持されるようです。
関数などへの基本的なコメントを書き方
関数などへコメントを書きたい場合は///
とスラッシュを3つ使います。通常のコメントのスラッシュ2つ(//
)やファイル全体などへのコメントのように!
記号を使った記法とは異なるので注意してください。
/// このプロジェクトのエントリーポイントです。
/// Hello, world!が出力されます。
fn main() {
println!("Hello, world!");
}
ドキュメントを出力してみると以下のようにFunctionsの節で設定したコメントが反映されていることが確認できます。
$ cargo doc --open
モジュール内のインターフェイスにもコメントを付与してみます。
pub mod sample_module {
//! 整数値を取得するインターフェイスを定義したサンプルの
//! モジュールです。
/// 整数の2を返却します。
pub fn get_two() -> i32 {
2
}
/// 整数の3を返却します。
pub fn get_three() -> i32 {
3
}
}
ドキュメントを出力してみます。ドキュメントのルートのページは特に変わっていません。モジュール内のものなどはそのモジュールページで確認する必要があるため、モジュールのリンク(今回はsample_module部分)をクリックしてそのモジュールのページに遷移してみます。
そうすると先ほどモジュール内の各インターフェイス(get_twoなど)に追加したコメントが反映されていることを確認できます。
また、関数はそれ自体もリンクになっており、クリックするとその関数のさらなる詳細を確認できます。まだ今のサンプルではほとんど記述がありませんが、返却値の型(今回はi32
)などが反映されていることが確認できます。
コメント内ではマークダウンが使える
//!
や///
のドキュメンテーションコメント内ではマークダウンが使えます。試してみたところ、一部で使えないものはあったものの大半は使えるようです。改行ではGitHubなどと同様に行末に半角スペースを2つ付与する必要があります。Qiitaのようにただの改行だけでは改行されません。
//! ドキュメンテーションコメントの説明用に作ったサンプルの
//! プロジェクトです。
//! 改行を入れたい場合はGitHubなどのマークダウンと同様に行末に
//! 半角スペースを2つ入れます。ただのコメント内の改行だけでは改行が
//! 無視されます。ただしドキュメント上では半角スペースが入るようです。
//! *斜体テスト*
//! **太字テスト**
//!
//! - リスト1
//! - リスト2
//!
//! * リスト3
//! * リスト4
//!
//! [リンクテスト](https://qiita.com/)
//!
//! # 見出しテスト1
//! テストテキスト1
//!
//! ## 見出しテスト2
//! テストテキスト2
//!
//! ~~打消し線テスト~~
//!
//! > 引用部分
//!
//! 水平線:
//!
//! ---
//!
//! インラインコードテスト : `println("Hello!");`
//!
//! ```
//! println!("コードブロックテスト");
//! ```
//!
//! テーブル記法
//!
//! | 見出し1 | 見出し2 |
//! |:-----------|:------------|
//! | 猫 | 犬 |
//! | 兎 | 鳥 |
/// このプロジェクトのエントリーポイントです。
/// Hello, world!が出力されます。
/// **太字テスト**
/// *斜体テスト*
///
/// # 見出し1
fn main() {
println!("Hello, world!");
}
...
見出しについて
マークダウンなので見出しが使えますが、コード例(# Examples
)やランタイムエラーになる条件(# Panics
)、安全に使うための指示(# Safe
や# Unsafe
)などは汎用的に使われる見出しとして公式ドキュメントや公式本に書かれています。
JSDocでいうところの@param
や@return
、PythonでのNumPyスタイルでのdocstringでいうところのParameters
やRaises
、Examples
などの記述に近いものになります。
例えばCrate level docs are thorough and include examples (C-CRATE-DOC)のドキュメントから引用させていただくと、以下のように# Panic
などの書き方が紹介されています。
/// Inserts an element at position `index` within the vector, shifting all
/// elements after it to the right.
///
/// # Panics
///
/// Panics if `index` is out of bounds.
/// # Panics
///
/// This function panics if `T`'s implementation of `Display` panics.
pub fn print<T: Display>(t: T) {
println!("{}", t.to_string());
}
引数や返却値に対するコメント
JSDocの@param
やPythonのNumPyスタイルのdocstringのParameters
のような個別の引数に対するコメントはどうやるのだろう?と調べていたのですが、これは今のところ公式的には特にルールなどは決まっていない?ようです。
ただし慣習的には以下のように/// * `<引数名>` - <引数の説明>
としているケースがあるようです。以下stackoverflowからの引用です。
I've seen the following style used in some of the examples:
/// Brief.
///
/// Description.
///
/// * `foo` - Text about foo.
/// * `bar` - Text about bar.
fn function (foo: i32, bar: &str) {}
How do you document function arguments?
Rustだと基本的に関数などに型を書く形となるため、引数の型などは省略されているようです(ドキュメントには型情報が反映されますし、コード上ではその型を見れば分かるのでドキュメンテーションコメント側には不要かなと)。
実際に書いてドキュメントで確認してみます。
pub mod sample_module {
//! 整数値の取得や計算などを行う。インターフェイスを定義したサンプルの
//! モジュールです。
...
/// 引数に渡された2つの値を合算した値を取得します。
///
/// * `x` - 合算する1つ目の値。
/// * `y` - 合算する2つ目の値。
pub fn sum_two_values(x: i32, y: i32) -> i32 {
x + y
}
}
マークダウンのおかげで大分見やすい形に出力されています。
VS Code上でマウスオーバーしてみてもそれっぽく表示されています。
また、公式のRustのリポジトリにはこの書き方以外にも引数個別にコメントが設定できたら読みやすいのでは?とissueが追加されています。まだclosedにはなっておらずサポートされていないものとなりますが将来もしかしたら反映されるかもしれません。以下にissueで記載されていた例のものを一部引用しておきます。
/// Supplies a new frame to WebRender.
///
/// Non-blocking, it notifies a worker process which processes the display list.
///
/// Note: Scrolling doesn't require an own Frame.
pub fn set_display_list(
&mut self,
/// Unique Frame ID, monotonically increasing.
epoch: Epoch,
/// Background color of this pipeline.
background: Option<ColorF>,
/// Size of the viewport for this frame.
viewport_size: LayoutSize,
...
) {...}
返却値に関しては調べても資料が見つかりませんでした。ただし以下の記事で引数部分に# Arguments
という見出しを付与している例を見つけたので、返却値に関しても記述したい場合はマークダウンに対応しているのでプロジェクト側のコーディング規則で# Arguments
と# Returns
といった形で記述するとかでもいいかもしれません。
/// # Arguments
///
/// * `byte_start` - The byte offset where the slice of `str` starts.
/// * `byte_len` - The number of bytes from `str` to use.
How to rustdocから引用。
ドキュメントのテストを行う
Rustではビルトインだけでドキュメント内の例で記述されたコードなどに対するテストを行うことができます(Pythonのdoctestに近いものになります)。ただ、バイナリクレートだとテストが実行されない?ようでライブラリクレートのプロジェクトを作ってそちらで進めていきます。
$ cargo new doclib --lib
$ cd doclib
以下のように、Examplesの見出し内にマークダウンのコードブロックの記述(```)を使って内部にRustのコードを書いていきます。また、ここではassert_eq!のマクロを記述しています。
/// 引数に渡された2つの値を合算した値を取得します。
///
/// # Arguments
///
/// * `x` - 合算する1つ目の値。
/// * `y` - 合算する2つ目の値。
///
/// # Examples
///
/// ```
/// let summed: i32 = doclib::sum_two_values(10, 20);
///
/// assert_eq!(summed, 30);
/// ```
pub fn sum_two_values(x: i32, y: i32) -> i32 {
x + y
}
このような記述はCargo側で自動で検出してコードを実行してくれるようです。実際にテストを流してみます。
テストを実行してみます。
$ cargo test
...
Doc-tests doclib
running 1 test
test src\lib.rs - sum_two_values (line 10) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
そうするとドキュメント内のテストが実行・通っていることが確認できます。これでアップデートなどでコードとドキュメントコメントが乖離して、サンプルコードが動かない・・・みたいなケースを軽減することができます。