Wonderfull Rust

ドキュメンテーション

Rust のドキュメンテーション文化

プログラミング言語のドキュメンテーションには,しばしば二つの問題が伴う:

  1. ドキュメントを書く動機が弱い: 追加の作業が必要で,コードの変更に追従しなければならない
  2. ドキュメントとコードが乖離する: 時間とともにドキュメントが古くなり,実際のコードの振る舞いと一致しなくなる

Rust はこの両方の問題に対して,言語とツールチェーンのレベルで解決策を提供している.

ドキュメントコメント

Rust のドキュメントコメントは ///(アイテムの外側)または //!(アイテムの内側)で始まる:

/// 2 つの数値を加算する.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(1, 2);
/// assert_eq!(result, 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
//! # My Crate
//!
//! `my_crate` は数学的な操作を提供するライブラリです.
//! このドキュメントはクレートレベルの説明として機能します.

ドキュメントコメントは Markdown で記述する.見出し,リスト,コードブロック,リンク,表 — Markdown のすべての構文が使える.

セクションの慣習

Rust のドキュメントには慣習的なセクションがある:

/// TCP ストリームからデータを読み取る.
///
/// # Arguments
///
/// * `buf` - データを格納するバッファ
///
/// # Returns
///
/// 読み取ったバイト数を返す.
///
/// # Errors
///
/// ソケットが閉じている場合,`io::Error` を返す.
///
/// # Panics
///
/// `buf` の長さが 0 の場合,パニックする.
///
/// # Safety
///
/// (unsafe 関数の場合) この関数を安全に呼ぶための前提条件を記述する.
///
/// # Examples
///
/// ```no_run
/// use std::net::TcpStream;
/// use std::io::Read;
///
/// let mut stream = TcpStream::connect("127.0.0.1:8080").unwrap();
/// let mut buf = [0u8; 1024];
/// let n = stream.read(&mut buf).unwrap();
/// ```
pub fn read(buf: &mut [u8]) -> io::Result<usize> {
    // ...
}
セクション 用途
# Examples 使用例(最も重要)
# Errors Result を返す関数で,どのようなエラーが発生しうるか
# Panics パニックする可能性がある条件
# Safety unsafe 関数の安全性に関する前提条件
# Arguments 引数の説明

ドキュメントテスト (Doc Tests)

Rust のドキュメンテーションで最も革新的な機能が ドキュメントテスト だ.

ドキュメントコメント内のコードブロック(``` で囲まれたもの)は,cargo test 実行時に 自動的にテストとして実行される:

/// 数値が偶数かどうかを判定する.
///
/// # Examples
///
/// ```
/// assert!(my_crate::is_even(4));
/// assert!(!my_crate::is_even(3));
/// ```
pub fn is_even(n: i32) -> bool {
    n % 2 == 0
}
cargo test
   Doc-tests my_crate

running 1 test
test src/lib.rs - is_even (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored

これがなぜ革新的か:

  1. ドキュメントがコードと乖離しない: ドキュメント内のコード例が実際にコンパイル・実行されるため,API が変更されるとドキュメントテストが失敗する
  2. ドキュメントを書く動機が生まれる: ドキュメントコメントのコード例がそのままテストになるため,テストを書く行為とドキュメントを書く行為が一体化する
  3. サンプルコードの正しさが保証される: ユーザーがドキュメントのコード例をコピー&ペーストしたとき,それが動作することが保証される

ドキュメントテストのオプション

/// # Examples
///
/// コンパイルが通ることだけを確認(実行はしない):
/// ```no_run
/// let mut file = std::fs::File::create("output.txt").unwrap();
/// ```
///
/// コンパイルエラーになることを期待:
/// ```compile_fail
/// let x: i32 = "hello";
/// ```
///
/// テストとして実行しない(擬似コードや説明目的):
/// ```ignore
/// // この例は外部サービスが必要
/// let response = fetch_from_api().await;
/// ```
///
/// 隠れた行(ドキュメント表示では非表示,テストでは実行される):
/// ```
/// # use std::collections::HashMap;
/// # fn main() {
/// let mut map = HashMap::new();
/// map.insert("key", "value");
/// assert_eq!(map["key"], "value");
/// # }
/// ```

# で始まる行はドキュメントの HTML 表示では非表示になるが,テスト実行時にはコードとして含まれる.これにより,use 文や main 関数のボイラープレートを隠しつつ,テストとしては完全なコードを実行できる.

cargo doc

cargo doc は,プロジェクト内のすべてのドキュメントコメントから HTML ドキュメントを生成する:

cargo doc --open

生成されるドキュメントには:

  • すべての公開アイテム(関数,構造体,enum,トレイト,モジュール)の API リファレンス
  • 型シグネチャ,トレイト実装の一覧
  • ドキュメントコメントの Markdown レンダリング
  • ソースコードへのリンク
  • 検索機能

さらに,依存クレートのドキュメントも一緒に生成されるCargo.toml に書かれたすべての依存クレートのドキュメントがローカルに生成され,クレート間のリンクも正しく解決される.

ドキュメント内リンク

ドキュメントコメント内で,他のアイテムへのリンクを書ける:

/// [`Vec`] と同様のインターフェースを持つ.
///
/// 詳細は [`Self::push`] を参照.
///
/// [`HashMap`](std::collections::HashMap) も参考にするとよい.
pub struct MyCollection {
    // ...
}

rustdoc がこれらのリンクを自動的に解決し,正しい URL に変換する.リンク先が存在しない場合,コンパイル警告が出る.

docs.rs

docs.rs は,crates.io に公開されたすべてのクレートのドキュメントを自動的にホスティングするサービスだ.

クレートを cargo publish すると,docs.rs が自動的に cargo doc を実行し,ドキュメントを公開する.クレート作者が追加の作業を行う必要はない.

すべての Rust クレートが https://docs.rs/<crate-name> という統一的な URL でドキュメントにアクセスできる.これは Node.js (npm) や Python (PyPI) のエコシステムにはない,Rust 独自のインフラだ.

ドキュメンテーションの質を支えるエコシステム

Clippy のドキュメント関連リント

Clippy には公開 API のドキュメントに関するリントも含まれている:

// clippy::missing_docs_in_private_items (pedantic)
// pub なアイテムにドキュメントコメントがない場合に警告

// clippy::missing_errors_doc
// Result を返す pub 関数に # Errors セクションがない場合に警告

// clippy::missing_panics_doc
// パニックしうる pub 関数に # Panics セクションがない場合に警告

#[doc] 属性

ドキュメントコメントは実際には #[doc] 属性のシンタックスシュガーだ:

/// これはドキュメントコメント
pub fn foo() {}

// ↑ これは以下と同等
#[doc = "これはドキュメントコメント"]
pub fn foo() {}

この事実を利用して,ドキュメントをプログラム的に生成することもできる:

macro_rules! make_documented_fn {
    ($name:ident, $doc:expr) => {
        #[doc = $doc]
        pub fn $name() {}
    };
}

make_documented_fn!(hello, "挨拶を行う関数.");

まとめ

Rust のドキュメンテーションシステムの素晴らしさは,個々の機能だけでなく,それらが統合されたエコシステムとして機能している点にある:

要素 役割
/// / //! Markdown によるドキュメントコメント
ドキュメントテスト コード例が自動的にテストされる
cargo doc HTML ドキュメントの生成
docs.rs 公開クレートのドキュメント自動ホスティング
Clippy ドキュメントの欠落を検出
ドキュメント内リンク アイテム間の参照を自動解決

この統合により,Rust エコシステムのドキュメントの質は他の言語と比較して著しく高い.crates.io のクレートの多くが,充実した API ドキュメントとテスト済みのコード例を持っている.

ドキュメンテーションはしばしば「書くべきだが書かれないもの」の代表例だ.Rust はこの問題に対して,「ドキュメントを書くことが自然にテストを書くことになる」という設計で解決した.正しさの保証と利便性を両立させる — これもまた,Rust の設計哲学の一つの表れだ.

本に戻る