Rustゼロクラッシュ講座
注: 本章の内容は改訂される可能性があります。
前章では、ソフトウェアセキュリティの概念を紹介するために、RustライブラリとCLIツールを一通り見てきました。 本章ではRust言語そのものに焦点を当て、その構文、機能、慣習を概観します。
ただし、Rustのすべてを扱うわけではありません。 Rustは大きな言語です。 CよりもC++にはるかに近いものです。 私たちが気に入っている包括的なRust本であるProgramming Rust1は、700ページを超える大著で、言語機能を容赦なく列挙しています。 これは驚異的な本であり、本書にも大きな着想を与えてくれました。 しかし、それを読み通すには、コストコのミニ樽入りホールビーンコーヒーを何樽も消費するような持久力が必要です。
課題の一部は、Rustが提供する機能の幅広さそのものにあります。 比較的新しい言語であるRustには、後知恵の利点があります。つまり、先行する言語の成功した側面を自由に選び取れるのです。
これには、OCamlの代数的データ型、C++の単相化、Schemeの衛生的マクロなどが含まれます2。 Rustチームは一貫した設計を目指していますが3、この言語はいくつもの影響をうまく取り扱っています。
幸いなことに、Rustで生産的に作業するために、Rustを網羅的に理解する必要はありません。 このセクションでは、重要な概念を先取りして紹介します。Rustのスニペットを読み書きし始めるのに十分な内容です。 本書の残りの部分では、組み込みに適した高保証ライブラリを構築しながら、それらの概念を定着させます。
その基礎があれば、実世界のRustプログラムを自分で書く準備が整います。 また、プロジェクトの必要に応じて、追加の言語機能(スマートポインタ、チャネル、async、マクロなど)の学習にも取り組めるようになります。
Rustのツアーは、やや短めの6つのパートに分けます。
-
低レベルのデータ表現 - プリミティブ、タプル、配列、参照、スライス。
-
高レベルのデータ表現 - 構造体、列挙型、ジェネリクス、トレイト。
-
制御フロー - 条件文、ループ、パターンマッチング。
-
所有権の原則 - Rustの最も斬新な機能の中核原則を理解する。
-
実践における所有権 - 日々所有権を扱うための概念。
-
エラー処理 - 失敗の伝播、および/または可用性の維持。
実地で検証済みの保証ガイドラインを重視する
本書は、堅牢で信頼性が高く、安全なシステムを構築するための入門書です。 したがって、本章タイトルのゼロクラッシュというしゃれがあります。
実践可能な保証技法を重視するために、Rustのツアーを、確立された業界標準の文脈で位置付けます。 最も容赦のない本番環境で検証されてきた知見に基づくものです。
Motor Industry Software Reliability Association (MISRA) C4ガイドラインは、その頭字語が示すとおり、もともとは自動車業界向けに作成されたCソフトウェア開発ルールの集合です。
スタイルガイドとは異なり、MISRA Cは安全性が重要なシステムの開発者向けにベストプラクティスを概説しています。 これは信頼性、セキュリティ、保守性を最大化することを意図しています5。 バグが生命を危険にさらす可能性のあるシステムのためのものです。
現在、これらのガイドラインは、航空宇宙、防衛、通信、医療機器の各業界で広く使用されています(DO-178C6やISO-262627のような業界固有のフレームワークに加えて)。 最新バージョン8では、次のように紹介されています。
MISRA Cガイドラインは、間違いを犯す機会が取り除かれる、または減らされるC言語のサブセットを定義する。 安全関連ソフトウェアの開発に関する多くの標準では、言語サブセットの使用が要求または推奨されており、これはセキュリティ、高い完全性、または高い信頼性の要件を持つ任意のアプリケーションの開発にも使用できる。
MISRA Cは何十年にもわたって検証され、洗練されてきました。 規制上の認証の外であっても、これらは高保証システムを構築するための実践的なガイドラインです。
Rustの中核設計は、安全で信頼性の高いソフトウェアの構築に直接適用できます。
unsafeキーワードを使用しないため、本書はRust言語の安全なサブセットを紹介していると言えるでしょう。
本書の目的における「安全なサブセット」
Rustプログラミング言語の「安全なサブセット」を真に構成するものは、現在の標準化および研究活動の対象です。 本書では、安全なサブセットを形式的に定義しようとはしません。
代わりに、コアプロジェクトでは、実務のエンジニアが「安全なサブセット」と見なせるものに自分たちを制限するために、クレート全体に適用される2つのマクロを使用します。
#![forbid(unsafe_code)]:unsafeキーワードの使用はコンパイル時エラーになります。これは、Rustの静的保証を最大化するのに役立ちます。
#![no_std]: 標準ライブラリの機能(unsafeコードを含みます)は使用しません。より厳密には、すべての動的メモリ使用をオプトアウトします。外部アロケータに依存しないことには、一定の堅牢性上の利点があります。コアプロジェクトはオープンソースライブラリ9に基づいているため、これらの制約内で作業することが、自明でないコードベースに対しても実行可能であることがわかっています。
さて、Rustは新しい言語であるため、安全性が重要な環境での使用についてはまだ広く認証されていません。ただし、これは業界での取り組み10および研究11が進められている分野です。 自動車(ISO 26262)および産業(IEC 61508)用途向けの認定済みRustコンパイラはありますが、MISRA Cガイドラインに対応するRust版はありません。 まだありません。
多くのMISRA CルールはC言語に固有です。 残りの一部を2つのカテゴリに分け、区別するために次のラベルを使用します。
-
Rustにより自動化(AR): 一貫して従うことが容易なルール、または慣用的なRustで自然に表現できるルール。どのRustプログラムでも、コンパイルできるなら、このカテゴリに従っている可能性が高いです。
-
Rustでも信頼性高く適用可能(RR): 正確性と堅牢性を優先するプログラムの設計および実装に一般的に適用できるルール。Rustでも容易に適用できますが、自動ではありません。プログラマ側の意識的な努力が必要です。
Rust言語の安全なサブセットを紹介する中で、該当するMISRA C8ルールを時折取り上げます。 本章と本書全体の両方で、上記のラベルのいずれかを前に付けます。
予告として、これから書くコアライブラリで準拠する3つのMISRA Cルールを示します(ただし、構築または使用する開発ツールについては、安全性が重要ではないため対象外です)。
[RR, 指令 4.1] 実行時障害を最小限に抑える8
[RR, 指令 4.12] 動的メモリ割り当てを使用してはならない8
[RR, 規則 17.2] 関数は自身を再帰的に(直接または間接に)呼び出すことはできない8
ここでは根拠を省いていることに注意してください。 上記の3つの規則が制約的に見える場合、それらは説得力を持ちます。 幸い、Rust ではこの種の高保証基準を満たすことが実現可能であり、かつ人間工学的です。
MISRA C に対する独自の見方
著作権を尊重するため慎重を期し、各 MISRA 規則の「見出し」については大まかな言い換えのみを提供します。その正確な文言、完全な説明、根拠、例外、カテゴリなどは提供しません。 これは、MISRA 規則を列挙している学術出版物12が採用しているのと同じアプローチです。
いくつかの場合、私たちの言い換えでは MISRA C Guidelines には存在しない Rust 固有の用語を導入します。 MISRA 規則を Rust に対応付ける先行研究13とは異なり、私たちは網羅性を目指しているわけではありません。 保証の概念を学ぶ目的で規則を抽出しています。
本章で言及する MISRA の規則と指令は、次のように分類できます。
大まかに言えば、指令とは、決定的かつ普遍的な方法で記述するのが難しい MISRA 規則です。 指令は、複雑なシステムではチェックや検証がより困難になる傾向があります。 一方で、規則は完全に捉えることが可能です。 多くの場合、静的解析ツール(Rust コンパイラなど)によって正確に検証できます。
繰り返しますが、私たちの MISRA 規則と指令のサンプルは網羅的ではないことに注意してください。 あなたがプロのセーフティまたはセキュリティエンジニアであれば、MISRA C 2012 Guidelines の完全版を MISRA から直接購入することをお勧めします。 広く採用されているベストプラクティスを理解することは、プロジェクトが使用する具体的なツールチェーンに関係なく価値があります。
Rust におけるソフトウェアエンジニアリング
高保証であるかどうかにかかわらず、現代の開発は言語構文や言語機能以上のものです。 それにはツール、プロセス、そして最も重要なものとして人が関わります。外部の顧客と内部のチームです。
効果的なプロセスを実装し、ステークホルダーのニーズに応える方法を学ぶ最良の方法は、プロとしての経験です。 本書ではツールに焦点を当てます。
前章で clap を使用したことで、サードパーティライブラリをビルドに統合する感覚をすでに得ました。
また、Rust の組み込みのファーストパーティ単体テストフレームワークを活用して、私たちの RC4 実装を公式テストベクターに照らして検証しました。
とはいえ、日々の開発タスクを支援するために cargo ができることについては、まだ表面をかすめただけです。
The Cargo Book14 では、より完全な概要が提供されています。
本章では、Rust のツールエコシステムの構成要素を、ファーストパーティとサードパーティの両方からさらにいくつか取り上げます。 また、本番システムで安定性がどのように実現されるのかを理解するために、Rust のリリースサイクルについても説明します。 より一般的には、成功するソフトウェアプロジェクトの重要な柱であるコード構成について取り上げます。
簡単な前提知識: スタック、ヒープ、値
本章では、「スタック」と「ヒープ」という2つの技術用語をときどき使用します。 この文脈では、これらの用語は2種類の異なるメモリ位置を指します。 同じ名前のデータ構造のことではありません(残念な専門用語の過負荷です)。
次章ではメモリについて詳しく説明します。 今のところは、次のように考えてください。
-
スタックメモリは、すぐに利用可能な短期的な(関数呼び出しの期間だけ生存する)ストレージです。ただし、固定サイズの変数しか格納できません。
-
スタックの仕組みは CPU ハードウェアと密接に関係しています。実際、多くのプロセッサには「スタックポインター」と呼ばれる特定のレジスタがあります。
-
スタックメモリはスタックデータ構造のように動作します。メモリの「フレーム」は*後入れ先出し(LIFO)*です。
-
整数と配列はデフォルトでスタック上に格納されます。
-
-
ヒープメモリは、明示的に要求し、後でクリーンアップしなければならない長期的な(解放されるまで生存する)ストレージです。ただし、サイズが実行時に決定される変数を格納できます。
-
ヒープの仕組みはソフトウェアによって処理されますが、DRAM ハードウェアに対応します。通常は OS15 と連携して動作するメモリ割り当てライブラリが、RAM のチャンクを管理する複雑なロジックを実装します。
-
ベクターと非リテラル文字列は通常、ヒープ上に格納されます。
-
スタックとヒープの区別はコンピューターアーキテクチャ上の関心事であり、システムプログラミング言語の構文に現れる必要があります。 「ハードウェアに近い」プログラミングには、ハードウェアとソフトウェアのインターフェイスを反映したメンタルモデルが必要です。
「値」も本章で使用する用語です。 これはあらゆる種類のプログラミング言語にまたがる概念です。
- 値とは、型付けされたデータの、メモリ位置に依存しない具体的なインスタンスです。
たとえば、let string_literal = "Hello, World!"; において、string_literal は値 "Hello, World!" が代入された変数(ラベル)です。この値には2つの部分があります。
-
型(ここでは
T: &'static str。このシグネチャの読み方は後で分解して説明します) -
具体的なビットパターン(特定の UTF-8 文字列
"Hello, World!"をエンコードする何か)。
それでは、ゼロクラッシュコースを始めましょう。
学習成果
- 高保証ソフトウェアを書くための主要なガイドラインを学ぶ。
- 「未定義動作」とその影響を理解する。
- Rust 言語の中核機能を学び、Rust のスニペットを読み書きすることに慣れる。
- 日々のソフトウェアエンジニアリング作業を容易にするために必須の Rust ツールを学ぶ。
-
[個人的なお気に入り] Programming Rust: Fast, Safe Systems Development. Jim Blandy, Jason Orendorff, Leonora Tindall (2021). ↩
-
The Rust Reference: Influences. The Rust Team (2021). ↩
-
Josh Triplett on Building the Build System of his Dreams. Sean Chen (2022). ↩
-
Assessing the Value of Coding Standards: An Empirical Study. Cathal Boogerd, Leon Moonen (2008). MISRA C 2004 標準を評価したこの論文は、72 個の MISRA 規則のうち欠陥検出に有意に効果的だったのは 12 個だけであり、特定の規則に従うことが、直感に反して、実際には欠陥率を増加させる可能性があると主張しています。これらの結論には議論の余地があり、MISRA C や同様のコーディング標準は、いくつかの業界で引き続きベストプラクティスとなっています。同じ著者による後続研究を除けば、同様の結論に至った他の研究は見つかりませんでした。しかし、完全性のためには、そのような主張にも言及する価値があります。読者には健全な懐疑心を保つことをお勧めします。少なくとも、コーディング標準の影響と適用にはニュアンスがあることには同意できます。 ↩
-
MISRA C: 2012 Guidelines for the use of the C language in critical systems (3rd edition). MISRA(2019)。 ↩ ↩2 ↩3 ↩4 ↩5
-
Towards Rust for Critical Systems. Andre Pinho, Luis Couto, Jose Oliveira(2019)。 ↩
-
The MISRA C Coding Standard and its Role in the Development and Analysis of Safety- and Security-Critical Embedded Software. Roberto Bagnara, Abramo Bagnara, and Patricia Hill(2018)。 ↩
-
MISRA-Rust. Shea Newton(2022年参照)。 ↩
-
The Cargo Book. The Cargo Team(2022年参照)。 ↩
-
ユーザー空間の「メモリアロケータ」は、必要に応じてヒープ容量を増やすために、OS に対して「システムコール」を発行できます。プログラムがヒープメモリを使用する場合、このランタイムサポートライブラリにリンクしなければなりません。これは非常に一般的であり、ほとんどのプログラムはこのように動作します。 ↩