- LinuxカーネルAPIの文書がどれほど不完全か、そしてRustがその問題の一部をどのように解決するかについて、人々は十分に認識していないと思う
- 複数のサブシステム向けにRust抽象化を書いたが、APIを安全に使う方法を完全に理解するには、ほぼすべてのケースでCのソースコードを読む必要があった
- 関数シグネチャと関連する文書コメント、あるいは明示的な文書だけでは、APIの安全な使い方を完全に把握するのは難しい
- ロックを保持している必要があるのか
- 参照カウント引数が参照を渡すのか、それとも自身で参照を取得するのか
- コールバック呼び出し時にロックが保持されるのか、それとも自分で取得しなければならないのか
- freeコールバックに特別な点があるのか
- 意図されたロック順序は何か
- 一部の操作で、状況によってロックを使える特殊なケースがあるのか
NULL引数が許容されるか、その有効な使い方は何か
- エラー時に参照カウントに何が起きるのか
- 返された参照カウント付きポインタがすでにインクリメント済みなのか、それとも渡された引数の参照からの暗黙の借用なのか
- 戻り値が常に有効なポインタなのか、
NULLになり得るのか、あるいはERR_PTRの可能性もあるのか
- 間接引数を通じて返されたポインタが、エラー時に
NULLへクリアされるのか、そのまま残るのか
- 返り値のポインタが不要なときに
NULL ** を渡すのが有効なのか
- 要件が合理的であっても文書化されていないことがあった
- 要件が柔軟すぎたり複雑すぎたりして、Rust抽象化を書く際に安全な使い方へ絞り込むため主観的な判断を下さなければならないこともあった
- 安全性を確保するため、抽象化の内部に追加のロックを導入しなければならないこともあった
- Cコードをもう少し直交的で論理的、かつ使いやすくするために、多少の変更を加える必要があることもあった(例: ロック保持状態で使うアンロック関数を公開する)
- ロックが微妙で、安全なRust抽象化は書けても、デッドロック防止のために使い方と解放順序に注意が必要だという大きな文書コメントが必要なこともあった(Rustは本質的にデッドロックを防がない)
- Cコードをもっと合理的にしないと解決不可能だったこともあった(
drm_schedの場合)
- しかし多くの場合、Rust抽象化を書く際の妥協点は、Cコード設計の問題点と改善の方向性を示している
- 一般的なアプローチは「まずCコードをできるだけ変更せずにRustコードを書いて衝突を避け、その後で得られた教訓に基づいてCコードの変更を提案する」というものだ(まだ後者までは進んでいない)
- 結果として、ほとんどの場合はRust APIを見るだけで正しい使い方が分かる
- 参照カウント、
NULLポインタ、結果確認の漏れ、エラー時の参照解放などを心配する必要がない
- 適切なロック使用、参照取得の漏れや二重解放などを心配する必要がない
- エラー戻り値のエンコード方式を気にする必要がない
- そうしたことを間違えるとコードがコンパイルできないからだ
- もちろんAPIを誤用すること自体はまだあり得るが、最悪でもエラー返却かデッドロックで済む(デッドロックはlockdepで容易にデバッグでき、
Arc<>統合によって解放・参照解除まわりのロックミスも検出できる)
- 比較的厳密に文書化されているOpenFirmware/DeviceTree APIでさえ、Cではすべての規則に従うのが退屈でミスを起こしやすい
- ドライバのOFコードを見ると、参照リークの可能性が高い
- 多くのシステムでは
OF_DYNAMIC 付きでカーネルをコンパイルしないため、参照カウントが無視され、見つからず修正もされない
- しかし私が書いたOF Rust抽象化は参照カウントを自動処理するので、これを気にしなくてよい
- Cと比べたRustでのカーネルコーディングの利点
- Cでカーネルを書く場合、選択肢は2つしかない
- とにかく書いて、レビュアーが見つけてくれることを祈るか、デバッグで苦労するか
- コードを使う勇気を出す前に、何時間もかけて理解し、すべてを洗い出せることを願うか
- これはレビュアーやメンテナの負荷も増やす
- 文書化されていない隠れたルールをすべて守っているか確認するため、提出物を精査しなければならない
- 問題を見落とすこともあれば、問題が大きすぎてコードを大規模にリファクタリングしなければならないこともある
- Rustではこれらがすべて消える。コンパイルできれば安全で、誤動作や参照リークがない(
unsafeコードは例外だが、それだけをレビューすればよく、十分に文書化されているべきだというルールがある)
- もちろんコードレビューや特定サブシステムの専門家の助けは依然として必要だ。Rustが魔法のようにコードを完璧にしてくれるわけではない
- しかし、愚かな低レベルの問題やミスを取り除けるため、高レベルの問題に集中できる
- Linux開発者に対する立場
- 私はLinux開発者の不完全な文書化を責めてはいない
- Linuxカーネルは非常に複雑で、多くの微妙な問題を扱わなければならない
- ほとんどのユーザー空間APIは、安全に使うためのルールがはるかに単純だ
- カーネルは難しい
- 経験豊富なカーネル開発者でさえ、こうしたことを常に間違える
- これは技術の問題ではなく、人間がこの複雑なルールをすべて頭に入れ、毎回正確に実行するのは不可能だということだ
- 解決策
- 私たちにはツールが必要だ
- 解決策はRustだ。すべてのルールを一度コードと型システムにエンコードしてしまえば、二度と心配する必要がない
- コーディングスタイル論争の解決策が、自動フォーマッタにすべてのルールをエンコードすることと同じだ
- そうすれば、低レベルの安全性、所有権、同期の問題を心配するのをやめて、ドライバやサブシステム設計のような、より重要な高レベルのことに集中できる
- Rust for Linuxプロジェクトのコードフォーマット
- Rust for Linuxプロジェクトでは、実際に提出物に
rustfmt を適用している
- カーネルRustを書くとき、コードフォーマットやコードレビューでの不満を心配する必要はない
- ただ
make rustfmt を実行すればよい
GN⁺の見解
- この記事は、Linuxカーネル開発におけるAPI文書化と安全性の問題を的確に指摘している。C言語の限界とRustの利点をよく示している
- しかし、「Rustが唯一の解決策」という表現はやや誇張に見える。静的解析ツールなど、他の方法でも一部は改善できるだろう
- Rustはメモリ安全性など多くの問題を解決してくれるが、それでも綿密なコードレビューとテストは必要だ。銀の弾丸ではない
- Rustへの移行には、既存のCコードとの互換性や開発者の学習曲線など、さまざまな難しさがあり得る。段階的な導入が望ましいように見える
- Linuxカーネルの古い慣行や文化を改善するには、Rustだけでなく、文書化、メンタリング、コミュニケーションなど多方面の努力も必要になりそうだ
- 全体として、この記事はRustがLinuxカーネル開発で持つ潜在力と利点をよく示しつつ、過度な期待や盲信は戒めており、バランスの取れた視点を示している。Rustの導入は技術的にも文化的にも難しさがあるだろうが、長期的にはカーネルコードの安全性と保守性の改善に貢献すると期待される。
2件のコメント
Rust…個人的に勉強してみたことはありますが、まだうちの会社では導入していませんね。すでにC++で書いたものが山ほどありますし、既存の人員がRustをあらためて学び直さなければならないという問題もあるので……。すでにRustを本番環境に適用している会社が韓国にもあると聞きましたが、関連する経験などが共有されるといいなと思います。
Hacker Newsのコメント
RustやSwiftのような言語は表現力が高く、データ型やメソッドのスレッド安全性をコンパイラが示してくれる
Rustライブラリにはドキュメント不足のものが多い
RustをCのように使おうとして、借用チェッカーの問題で苦労した
&selfまたは&mut selfを確認することが重要だ&mut selfがある場合、インスタンスをスレッド間で共有するにはミューテックスを使う必要があるRust APIを見ると、ほとんどの場合は正しく使う方法がわかる
Rustの具体例として、データ保護のためにロックを使う方式がある
他の言語でも、APIを重複実装すればコードとドキュメントの明確さを高められる
Python拡張でCを使う際は呼び出し規約を理解しなければならないという問題がある
こういう人たちは英雄であり、素晴らしい仕事をしている
完全に自己文書化されたコードの実現に、さらに一歩近づいた