Rustdoc の内部
このページでは、rustdoc のパスとモードについて説明します。
rustdoc の概要については、“Rustdoc の概要” の章を参照してください。
クレートから clean へ
core.rs には、中心となる項目が 2 つあります。rustdoc::core::DocContext
struct と、rustdoc::core::run_global_ctxt 関数です。
後者は、rustdoc が rustc を呼び出して、rustdoc が引き継げる段階まで
クレートをコンパイルする場所です。
前者は、クレートをクロールしてドキュメントを収集する際に使用される状態コンテナーです。
クレートのクロールの主な処理は、clean/mod.rs で、clean_ で始まる名前を持つ
複数の関数を通じて行われます。
各関数は hir
または ty のデータ構造を受け取り、rustdoc が使用する clean 構造を出力します。
たとえば、ライフタイムを変換するこの関数です。
fn clean_lifetime<'tcx>(lifetime: &hir::Lifetime, cx: &mut DocContext<'tcx>) -> Lifetime {
if let Some(
rbv::ResolvedArg::EarlyBound(did)
| rbv::ResolvedArg::LateBound(_, _, did)
| rbv::ResolvedArg::Free(_, did),
) = cx.tcx.named_bound_var(lifetime.hir_id)
&& let Some(lt) = cx.args.get(&did).and_then(|arg| arg.as_lt())
{
return lt.clone();
}
Lifetime(lifetime.ident.name)
}
また、clean/mod.rs は、後でドキュメントページをレンダリングするために使用される
「clean 済み」の 抽象構文木
(AST) の型を定義します。
それぞれには通常、
rustc からの何らかの AST または 高水準中間表現
(HIR) の型を受け取り、それを適切な「clean 済み」の型に変換する
clean_* 関数が付随しています。
モジュールや関連項目のような「大きな」項目では、その clean 関数で
追加の処理が行われることもありますが、ほとんどの場合、これらの
impl は単純な変換です。
このモジュールへの「エントリーポイント」は
clean::utils::krate で、これは run_global_ctxt から呼び出されます。
clean::utils::krate の最初のステップは、
visit_ast::RustdocVisitor を呼び出し、モジュールツリーを中間の visit_ast::Module に処理することです。
これは、実際に
rustc_middle::hir::Crate をクロールし、次のような名前解決のさまざまな側面を正規化するステップです。
#[doc(inline)]と#[doc(no_inline)]の処理- インポート glob とサイクルを処理し、重複や無限の ディレクトリツリーが発生しないようにする
- 非公開項目の公開
useエクスポートをインライン化する、またはモジュールページに “Reexport” 行を表示する - ベース項目が hidden である場合に、
#[doc(hidden)]が付いた項目をインライン化するが、その #[macro_export]されたマクロを、それが再エクスポートとして定義されているかどうかに関係なく、 クレートルートに表示する
このステップの後、clean::krate は clean_doc_module を呼び出し、これが実際に
HIR 項目を clean 済みの AST に変換します。
これは、クロスクレートのインライン化が行われるステップでもあり、
そのためには rustc_middle のデータ構造を clean 済みの AST に変換する必要があります。
clean/mod.rs で行われるもう 1 つの主要な処理は、doc
コメントと #[doc=""] 属性を、Attributes
struct の別フィールドに収集することです。この struct は、手書きのドキュメントを持つものすべてに存在します。
これにより、プロセスの後半でこのドキュメントを収集しやすくなります。
このプロセスの主な出力は、対象クレート内の公開ドキュメント化可能な項目を記述する Item のツリーを持つ
clean::types::Crate です。
ガソリンスタンド以外は何でも Pass する(または: Hot Potato)
次の主要なステップに進む前に、clean 済みの AST に対して
いくつかの重要な「パス」が実行されます。
これらのパスのいくつかは lint やレポートですが、一部は項目を変更したり、新しい項目を生成したりします。
これらはすべて、[librustdoc/passes] ディレクトリに、1 パスにつき 1 ファイルとして実装されています。
デフォルトでは、これらのパスはすべてクレートに対して実行されますが、
private/hidden 項目の削除に関するものは、rustdoc に
--document-private-items を渡すことで回避できます。
前述の AST 変換の集合とは異なり、
パスは clean 済み のクレートに対して実行されることに注意してください。
- `calculate-doc-coverage` は、`--show-coverage`
フラグで使用される情報を計算します。
- `check-doc-test-visibility` は、`doctest` の可視性に関連する `lint` を実行します。
このパスは `strip-private` より前に実行されるため、
`run-lints` とは別にしておく必要があります。
- `collect-intra-doc-links` は、[ドキュメント内リンク](https://doc.rust-lang.org/nightly/rustdoc/write-documentation/linking-to-items-by-name.html)を解決します。
- `collect-trait-impls` は、クレート内の各アイテムについて `trait` の `impl` を収集します。
たとえば、ある `trait` を実装する `struct` を定義した場合、
このパスは、その `struct` がその `trait` を実装していることを記録します。
- `propagate-doc-cfg` は、`#[doc(cfg(...))]` を子アイテムへ伝播させます。
- `run-lints` は、`passes/lint` で定義されている `rustdoc` の `lint` の一部を実行します。
これは最後に実行されるパスです。
- `bare_urls` は、リンク化されていないリンクを検出します。たとえば、
`Go to https://example.com/.` のような Markdown 内のリンクです。これは、リンクをリンク化するために山括弧で囲むことを提案します:
`Go to <https://example.com/>.`
これは、<!-- date-check: may 2022 --> `rustdoc::bare_urls` `lint` の背後にあるコードです。
- `check_code_block_syntax` は、Rust コードブロック内の構文を検証します
(<code>```rust</code>)
- `html_tags` は、doc コメント内の無効な `HTML`(閉じられていない `<span>` など)を検出します。
- `strip-hidden` と `strip-private` は、すべての `doc(hidden)` と private アイテムを
出力から取り除きます。
`strip-private` は `strip-priv-imports` を含意します。
基本的に、その目的は公開ドキュメントに関係しないアイテムを取り除くことです。
このパスは、`--document-hidden-items` が渡された場合はスキップされます。
- `strip-priv-imports` は、クレートからすべての private なインポート文(`use`、`extern
crate`)を取り除きます。
これが必要なのは、`rustdoc` が *public* な
インポートを、アイテムのドキュメントをモジュールにインライン化するか、
そのインポートを含む "Reexports" セクションを作成することで処理するためです。
このパスは、これらのインポートがすべて実際にドキュメントに関連していることを保証します。
技術的には `--document-private-items` が渡された場合にのみ実行されますが、`strip-private`
でも同じことが達成されます。
- `strip-private` は、外部から見えないクレート内のすべての private アイテムを取り除きます。
このパスは、`--document-private-items` が渡された場合はスキップされます。
`librustdoc/passes` には [`stripper`] モジュールもありますが、これは
`strip-*` パス用のユーティリティ関数の集合であり、それ自体はパスではありません。
[`librustdoc/passes`]: https://github.com/rust-lang/rust/tree/HEAD/src/librustdoc/passes
[`stripper`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/passes/stripper/index.html
## clean から HTML へ
ここから、`rustdoc` における「第 2 フェーズ」が始まります。
このフェーズは主に
[`librustdoc/formats`] および [`librustdoc/html`] フォルダー内にあり、すべては
[`formats::renderer::run_format`] から始まります。
このコードは、`impl FormatRenderer` する型をセットアップする役割を担っており、
`HTML` の場合、それは [`Context`] です。
この構造体には、ドキュメントレンダリングを駆動するために `run_format` から呼び出されるメソッドが含まれます。これには次のものが含まれます。
* `init` は、検索インデックスおよび `src/` とともに `static.files` を生成します
* `item` は、アイテムの `HTML` ファイルそのものを生成します
* `after_krate` は、`all.html` のようなその他のグローバルリソースを生成します
`item` では、[Askama] テンプレートと手動の `write!()` 呼び出しの組み合わせによって、「ページレンダリング」が行われます。これは [`html/layout.rs`] から始まります。
テンプレートに変換されていない部分は、一連の `std::fmt::Display`
実装と、`&mut std::fmt::Formatter` を受け渡す関数の中に存在しています。
アイテムとドキュメントから実際に `HTML` を生成する部分は、[`html/render/print_item.rs`] で定義されている [`print_item`] から始まります。これは、レンダリングされる `Item` の種類に基づいて、複数ある `item_*` 関数のいずれかへ切り替えます。
探しているレンダリングコードの種類によって、おそらくそれは、
「`struct` ページにはどのセクションを出力すべきか」のような主要アイテムについては [`html/render/mod.rs`] に、
「他のアイテムの一部として where 句をどのように出力すべきか」のような小さなコンポーネント部分については [`html/format.rs`] に見つかるでしょう。
`rustdoc` は、手書きのドキュメントを併せて出力すべきアイテムに出会うたびに、Markdown パーサーとのインターフェイスを担う [`html/markdown.rs`] を呼び出します。
これは、Markdown 文字列をラップし、`HTML` テキストを出力するために `fmt::Display` を実装する一連の型として公開されています。
Markdown パーサーを実行する前に、脚注や表のような特定の機能を有効にし、Rust コードブロックへ(`html/highlight.rs` を介して)構文ハイライトを追加するよう、特別な注意が払われています。
また、[`find_codes`] という関数もあります。これは
`find_testable_codes` から呼び出され、テストランナーコードがクレート内のすべての `doctest` を見つけられるよう、Rust コードブロックを特にスキャンします。
[`find_codes`]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustdoc/html/markdown.rs.html#749-818
[`formats::renderer::run_format`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/formats/renderer/fn.run_format.html
[`html/format.rs`]: https://github.com/rust-lang/rust/blob/HEAD/src/librustdoc/html/format.rs
[`html/layout.rs`]: https://github.com/rust-lang/rust/blob/HEAD/src/librustdoc/html/layout.rs
[`html/markdown.rs`]: https://github.com/rust-lang/rust/blob/HEAD/src/librustdoc/html/markdown.rs
[`html/render/mod.rs`]: https://github.com/rust-lang/rust/blob/HEAD/src/librustdoc/html/render/mod.rs
[`html/render/print_item.rs`]: https://github.com/rust-lang/rust/blob/HEAD/src/librustdoc/html/render/print_item.rs
[`librustdoc/formats`]: https://github.com/rust-lang/rust/tree/HEAD/src/librustdoc/formats
[`librustdoc/html`]: https://github.com/rust-lang/rust/tree/HEAD/src/librustdoc/html
[`print_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/html/render/print_item/fn.print_item.html
[Askama]: https://docs.rs/askama/latest/askama/
### 最初から最後まで(または: [「あの最初の `Cell` たちから私たちまで、途切れない一本の糸が伸びている」][video])
[video]: https://www.youtube.com/watch?v=hOLAGYmUQV0
重要なのは、`rustdoc` は `HTML` 生成中であっても、型情報をコンパイラに直接問い合わせることができるという点です。
これは[以前はそうではありませんでした][didn't used to be the case]。また、`rustdoc` のアーキテクチャの多くは、それを行わないことを前提に設計されていました。しかし現在では
`TyCtxt` が `formats::renderer::run_format` に渡されており、これは
`HTML` と(<!-- date-check --> 2026年5月時点で unstable な)JSON 形式の両方の生成を実行するために使用されます。
この変更により、他の変更で "clean" [`AST`][ast] から、`TyCtxt` クエリから容易に導出できるデータを取り除けるようになりました。また、私たちは通常、
"clean" からフィールドを取り除く PR を受け入れます(これは soft-deprecated されています)。しかし、これは
`rustdoc` が置かれている他の 2 つの制約によって複雑になります。
* ドキュメントは、実際には型チェックに通らないクレートに対しても生成できます。
これは、`libstd` がサポートされているすべてのオペレーティングシステムを網羅する単一のドキュメントパッケージを持つ場合のように、相互排他的なプラットフォーム構成を網羅するドキュメントを生成するために使用されます。
つまり、`rustdoc` は `HIR` からドキュメントを生成できる必要があります。
* ドキュメントはクレートをまたいでインライン展開できます。
クレートのメタデータには `HIR` が含まれていないため、
`rustc_middle` のデータからインライン展開されたドキュメントを生成できなければなりません。
「clean」な [`AST`][ast] は、両方の入力形式に対する共通の出力形式として機能します。
clean には、`HIR` に直接対応しないデータもいくつかあります。たとえば、
auto trait に対する合成 `impl` や、`collect-trait-impls` パスによって生成される blanket `impl` などです。
追加のデータの一部は `html::render::context::{Context, SharedContext}` に格納されます。
これら 2 つの型は、将来的にマルチスレッドのドキュメント生成を行う場合に備えて `rustdoc` のデータを分離する手段として、また単に整理された状態を保つ手段として機能します。
* [`Context`] は、現在のページを生成するために使用されるデータを格納します。たとえば、そのパス、使用済みの `HTML` ID のリスト(重複する `id=""` を避けるため)、および `SharedContext` へのポインターなどです。
* [`SharedContext`] は、ページごとに変化しないデータを格納します。たとえば、`tcx` ポインターや、すべての型のリストなどです。
[`Context`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/html/render/context/struct.Context.html
[didn't used to be the case]: https://github.com/rust-lang/rust/pull/80090
[`SharedContext`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/html/render/context/struct.SharedContext.html
## その他の奥の手
ここまでで、Rust クレートから `HTML` ドキュメントを生成するプロセスについて説明しましたが、`rustdoc` が動作する主要なモードは他にもいくつかあります。
スタンドアロンの Markdown ファイルに対して実行することもできますし、Rust コードやスタンドアロンの Markdown ファイルで `doctest` を実行することもできます。
前者の場合、`html/markdown.rs` へ直接ショートカットします。必要に応じて、出力 `HTML` に目次を挿入するモードも含まれます。
後者の場合、`rustdoc` は `test.rs` で関連するドキュメントを取得するために同様の部分的なコンパイルを実行しますが、完全な clean およびレンダリングのプロセスを通る代わりに、はるかに単純なクレート走査を実行して、手書きのドキュメント*だけ*を取得します。
前述の `html/markdown.rs` 内の「`find_testable_code`」と組み合わせることで、テストランナーに渡す前に、実行するテストのコレクションを構築します。
`test.rs` で注目すべき場所の 1 つは `make_test` 関数で、ここで手書きの `doctest` が実行可能なものへと変換されます。
`make_test` に関する追加の読み物は
[こちら](https://quietmisdreavus.net/code/2018/02/23/how-the-doctests-get-made/)にあります。
## ローカルでのテスト
生成された `HTML` ドキュメントの一部の機能では、ページをまたいでローカルストレージを使用する必要がある場合がありますが、これは `HTTP` サーバーなしではうまく機能しません。
これらの機能をローカルでテストするには、次のようにローカル `HTTP` サーバーを実行できます。
```console
$ ./x doc library
# ドキュメントは `build/[YOUR ARCH]/doc` に生成されました。
$ python3 -m http.server -d build/[YOUR ARCH]/doc
これで、インターネット上でホストされている場合と同じようにドキュメントを閲覧できます。
たとえば、std の URL は rust/std/ になります。