なぜこの本なのか?
Rust の勢いには、いくつもの要因が連なっています。 無料で高品質な学習リソース1が利用できることは、間違いなくその 1 つです。 優れた公式テキストである The Rust Programming Language2 も含まれます。
では、なぜまた別の Rust の本が必要なのでしょうか? 実のところ、本書は 本当の意味では Rust についての本ではありません。ただし、読者がこの言語を学ぶ手助けをすることは主要な目標です。 本書は、根本的に重要でありながら、気後れするほど難しいトピックへの入口です。それは、システムプログラミングと低レベルセキュリティです。
より具体的には、システムに関するトピックとして、組み込みに適したデータ構造と、言語間の相互運用性を扱います。 セキュリティに関するトピックとしては、静的検証、動的なバグ発見、バイナリ悪用を扱います。
これらのトピックを取り上げることが、今後の皆さんの役に立つことを願っています。 あるいは、単に「物事がどのように動いているのかを理解したい」という欲求を満たすだけでもかまいません。
健全な懐疑心についての注記
本書は、技術的に正確で、データに基づき、実用的であることを目指しています。 しかし、本書には意見も含まれています。 ここで示される立場は、著者たちの信念と経験を反映しています。
あらゆる主張、とりわけセキュリティに関する主張は、批判的な視点を通して評価されるべきです。 これは本書に限ったことではなく、一般に当てはまります。 偏りのない情報などというものは存在しません。
健全な懐疑心という姿勢を培い、維持することをお勧めします。 それは、どのようなソフトウェアセキュリティの文脈でも大きな見返りをもたらします。
事実誤認を見つけた場合は、お知らせください。
なぜシステムプログラミングについて学ぶのか?
3 人のエンジニアに「システムプログラム」が何をするものか尋ねたら、おそらく 3 つの異なる答えが返ってくるでしょう。 これは、さまざまな業界やユースケースにまたがる幅広い分野です。
-
「システムプログラムは分散されており、低レイテンシで意思決定を行い、合意プロトコルを介してネットワーク上で協調するものです」と、1 人目のエンジニアは言うかもしれません。
- 例:分散データベースやフォールトトレラントサービス。
-
「違います」と、2 人目は口を挟むかもしれません。「システムプログラムは、ユーザー空間アプリケーションから見たハードウェアの姿を管理し、物理リソースの利用をスケジューリングして促進するものです」。
- 例:オペレーティングシステムやデバイスドライバー。
-
3 人目は不満そうな顔をして、「低消費電力デバイス上でセンサーデータを収集する緊密なフィードバックループはどうでしょうか? それはリソースをまったく共有していません!」と付け加えるかもしれません。
- 例:マイクロコントローラーや制御システム向けのファームウェア。
3 つの答えはすべて正しいものです。 共通する筋(しゃれのつもりです3)は、システムプログラムの目標がハードウェアリソースの制約と結びついているという点です。 それらは低レベルに位置します。 特殊用途のデバイスでは、それが存在する唯一のレベルです。
より一般的には、それはユーザーが直接やり取りする高レベルプログラムの基盤です。 リソースの効率的な利用、つまり性能は重要です。少しの低速化でも増幅され、それに依存するすべての高レベルプログラムに影響を及ぼします。
システムプログラムを素早く見分けるために、次のような質問をすることができます(経験則はすべてそうであるように、これは近似です)。
-
そのプログラムが応答性に優れていると感じる必要があるのは誰ですか?
-
人間: それはシステムプログラムではありません。人間の時間に制約されています(ミリ秒単位で測られます。これは私たちにとって実感できる最小の単位です)。
-
別のプログラム: それはシステムプログラムです。マシンの時間に制約されています(CPU サイクル単位で測られます。現代のプロセッサは毎ミリ秒に数百万サイクルを実行します)。
-
高レベルアプリケーションを書くことにしか関心がないとしても、低レベルアプリケーションについて少し(最後のしゃれです、約束します)理解しておくことは役に立ちます。 自分の下にあるレイヤーとより効果的にやり取りでき、スタック内の自分のレベルにあるボトルネックに対しても、同じ性能志向の考え方を適用できます。それがどのレベルであってもです。
レベル別の言語
Rust プログラミング言語はシステムソフトウェア専用に使われるわけではありませんが、それが強みであることは確かです。 Rust は、スクリプト言語(Python、Ruby、Lua など)やガベージコレクション付き言語(Java、Go、C# など)では動作できないスタックのレベルで実行できます。 常に存在するこれらの低レベルでは、メモリ安全ではないシステム言語(C と C++)が何十年にもわたって独占してきました。
より安全なシステムプログラミングを提供する言語はどれか?
プログラミング言語は、突き詰めれば単なる道具です。 言語の選択は熱のこもった議論を引き起こすかもしれませんが、結局のところ重要なのは、その仕事に最適な道具を選ぶことです。
利用可能な選択肢を認識していなければ、客観的ではいられません。 Rust の旅を始める前に、より安全なシステムプログラミングの代替となる 2 つの選択肢、Zig と Ada について簡単に触れておくべきでしょう。 Rust と同様に、どちらもネイティブにコンパイルされ、ガベージコレクションを使用せず、C 系言語に対する安全性の利点があります。 3 つの言語はいずれも、現代的な低レベルプログラミングに同程度に適しています。 3 つとも、特定の文脈では優れた選択肢です。
詳細な比較の代わりに、ミッション・クリティカルシステムおよびセーフティ・クリティカルシステムの開発における成熟度という観点から、3 つの選択肢を対比してみましょう。 ミッション・クリティカルシステムとは、本番環境で稼働しており、セキュリティや信頼性の障害が事業にとって高くつくものだと仮定します。 また、セーフティ・クリティカルシステムは物理現象を駆動するため、セキュリティや信頼性の障害が人命、財産、または環境を危険にさらす可能性があるものだと仮定します。
-
Rust(成熟度:中)- Rust は 2015 年に 1.0 に到達し、ミッション・クリティカルな本番環境でのユーザーや用途が非常に豊富にあります4。Rust を安全クリティカル領域に持ち込むための現在の取り組みには、Ferrous Systems と AdaCore の協業5、AUTOSAR Working Group6、SAE International Task Force7、そして形式検証ツールに関する継続的な研究開発8が含まれます。本稿執筆時点で、Ferrocene9 Rust ツールチェーンは、自動車(ISO 26262 ASIL-D)および産業(IEC 61508 SiL4)ユースケース向けの認定を完了しつつあります。
-
Ada(成熟度:高)- Ada の 1.0 仕様である MIL-STD-1815-A10 は 1983 年にリリースされました。商用サポートされているコンパイラとランタイムライブラリは、DO-178B/C(航空)、ISO 26262(自動車)、IEC 61508(産業)、ECSS-Q-ST-80C(宇宙)などの規格での使用に対して、すでに認定されています11。Ada のサブセットである SPARK は、成熟した演繹的検証機能を提供します12。SPARK は、Rust の型システムに一部触発され、ヒープメモリ検証において近年進歩を遂げています13。
どのプログラミング言語であれ、それを学ぶことは開発者として成長するための素晴らしい方法です。 健全なコミュニティを持つ新しい言語は、時間の経過とともに独自のニッチを見つけ、その革新を洗練させていくかもしれません。 確立された言語は、現時点でより豊富なツールやライブラリエコシステムを提供できます。 利用可能な選択肢が複数あることは、開発者にとっても、最終的には顧客にとっても良いことです。
私たちは、Rust が今日の多くのプロジェクトにとって卓越したツールであり、明日のさらに多くのプロジェクトにとっても有力な選択肢になると信じています。
設計による安全性
Zig、Rust、Ada は、次のうち 1 つ以上に対してさまざまな戦略を採用しています。
安全機能を型システムに直接組み込む(コンパイル時の強制)
デフォルトのランタイム安全チェックを無効にするには明示的なオプトアウトを要求する(ランタイム検出)
安全性を言語の中核設計に組み込むことで、欠陥除去を確実にスケールさせやすくなります。 オプトインのベストプラクティスを教育したり、サードパーティのエラーチェッカーを使用したりする場合と比べてです。
その一方で、既存プロジェクトが新しい言語ツールチェーンを学習して採用するコストは高くなる可能性があります。 また、新しいプロジェクトで安全でない言語を選ぶことに妥当な理由がある場合もあります。 ツール選定は、状況に大きく依存する問題です。
なぜデータ構造ライブラリを構築するのか?
ホワイトボードでのコーディング面接が嫌な後味を残したのかもしれません。 大学のデータ構造の授業が、いまひとつ腑に落ちなかったのかもしれません。 あるいは独学だからこそ、このトピックが学術的で、ほとんど関係のないものに感じられるのかもしれません。 結局のところ、ライブラリはすでに書かれています。自分で二分探索木を実装することが一度もなくても、長く実りあるソフトウェアのキャリアを築くことはできます。
しかし、データ構造には特別なものがあります。 データ構造は、計算機科学理論の数学的厳密さと、効率的な実装という実践上の制約を組み合わせます。 それは、抽象と具象が交差する場所で何が可能なのかを垣間見せてくれる、まれな存在です。 いわば、数学というタイヤがコードという路面と接する場所です。
現代のプログラミング言語の標準ライブラリには、データ構造 API、つまり「コレクション」が含まれています。 これらの API は、その言語で書かれたほぼすべてのプログラムで使われます。 1 つのデータ構造実装がこれほど広く使われる可能性があるため、パフォーマンスは極めて重要です。 そのため、現実世界のデータ構造は通常、速度のために Rust や C のようなコンパイル型言語で実装されます。 これには、Python のようなインタプリタ型言語の標準ライブラリに含まれるものも含まれます14!
データ構造ライブラリを構築することは、複雑な機能要件の集合を取り、それをコンパクトで正しいコードへ変換することを伴います。
それは、戦略的なレベル(全体のアルゴリズム)と戦術的なレベル(使用している言語における、insert、get、remove などの個々の構造操作の仕組み)の両方で、目標を達成する道筋を示してくれます。
一般に応用できる問題解決スキルと、言語固有の開発スキルの両方を学ぶことになります。
コンパイル型 vs. インタプリタ型:
コンパイラ(C の
gccや Rust のrustcなど)は、ソースコードをネイティブバイナリ、つまり CPU が直接実行する命令で満たされた実行可能ファイルへ変換します。その結果、特定の CPU 命令セットアーキテクチャ(ISA - 例:x86、ARM、RISC-V など)向けに構築された、非常に高速なプログラムになります。インタプリタは、ソースコードを一度にひとかたまりずつ実行します。インタプリタプログラムは、構文木をたどることであなたのプログラムを実行します。これは実行が遅くなることを意味しますが、インタプリタ(おそらくそれ自体がネイティブバイナリ)が対応している限り、どの CPU に対しても移植性があります。
実際には、その境界線が常に完全に明確なわけではありません。Python は実際にはソースをバイトコード(インタプリタが理解する命令)へ変換します。これは一種のコンパイルです。
覚えておくべきことはこれだけです。システムソフトウェアは、効率が最優先事項であるため、ネイティブにコンパイルされなければなりません(CPU 命令に変換されなければなりません)!
Rust ではデータ構造は特に難しいのでは?
Rust のメモリ安全性の保証は、システムソフトウェアのセキュリティにおける大きな進化です。 業界での採用が広がり、ライブラリエコシステムが活況を呈している Rust は、システムプログラミングとセキュアコーディングの両方にふさわしい代表格です。
しかし一部のデータ構造は、コンパイラが安全性固有の性質を検査する方法のために、Rust で書くのが悪名高いほど難しい15ことで知られています。 これは、ある要素が別の要素に到達する方法を知っており、その逆も成り立つような構造(たとえば「循環参照」を持つもの)で特に当てはまります。 このためにあまりにも苛立ち、素早く膝を突き上げてキーボードを真っ二つに折り、Rust を完全に諦めてしまう開発者もいます!
だからこそ、あなたは高度なデータ構造から Rust の旅を始めることになります。 それは、Rust の最も難しい概念に正面から取り組むようあなたを押し出します。 厄介なデータ構造の Rust 実装がまだ存在しない場合でも、問題を Rust らしい方法で捉え直し、自分で実装するために必要なスキルを身につけることになります。
その能力こそが、Rust で成功するための足場です。最終的な目標が何であれ。 メモリがどのように機能し、Rust がそれをどのように管理するのかについてメンタルモデルを構築できれば、安全なシステムソフトウェアを出荷する道を順調に進んでいることになります。
セキュリティ以外では、なぜ Rust なのか?
Rust はパフォーマンスを最大化できるからです。
ムーアの法則16は限界に達し、物理法則は命令スループットとクロック速度に上限を設けました。 ボトルネックが実際にプログラムの CPU 時間である(たとえばネットワークレイテンシやディスク I/O の制限ではない)と仮定すると、パフォーマンスを改善する方法は通常 2 つあります。
- 問題に対して、より優れたアルゴリズムが存在するなら、それを実装する。
- 遅い処理を複数のコアに並列化する。 Rust のメモリ推論は、[多くの場合より現実的な] 後者の選択肢に役立ちます。コンパイラが信頼性の高い並行性を保証してくれるからです。 これは、アルゴリズムのロジックを自動的に並列化するという意味ではありません(それは依然として難題です)。また、すべての競合状態を防ぐという意味でもありません(たとえば、コンパイラはデッドロックについて推論できません)。
しかし、すべての「データ競合」(起こり得る競合状態の重要なサブセット)から解放されることは意味します。 それらは、「タイミング」(組合せ的なスレッドのインターリーブ)に関する前提の微妙な誤りによって引き起こされる、一見ランダムな状態破壊です。
並行プログラミングは、従来、正しく行うのが非常に困難でした。 性能上の利点を得るには、問題を捉え直すことと、予測不能な挙動をデバッグすることの両方が必要でした。 Rust は、より高い決定性を可能にすることで、そのデバッグの大部分を取り除きます。 この言語は、メモリ安全性の問題を解決すると同時に、並行性の問題を緩和します。
でも、これらは本当に自分のためのものなのか?
楽しめるなら、もちろんです!
システムプログラミングと低レベルセキュリティは気後れするようなトピックなので、特定の種類の人だけのものだと思うかもしれません。 従来は、実際そうでした。 現実的には、障壁はまだ存在します。
あなたの経歴や経験によっては、自分がシステムセキュリティの世界の一員である姿を思い描くのは難しいかもしれません。 しかし、あなたには確かにそれができます! それは簡単ではないかもしれません——しかし、可能です。
-
The Rust Programming Language。Steve Klabnik、Carol Nichol(2022 年アクセス)。 ↩
-
Ferrous Systems と AdaCore が Ferrocene で協力へ。Ferrous Systems(2022)。 ↩
-
AUTOSAR が Automotive Software の文脈におけるプログラミング言語 Rust の新しい Working Group を発表。AUTOSAR(2022)。 ↩
-
SAfEr Rust Task Force。SAE International(2022 年アクセス)。 ↩
-
Rust 検証ツール。Rust Formal Methods Interest Group(2021)。 ↩
-
1978 年、米国国防総省(DoD)は、安全性が重要な組込みシステムに特化したプログラミング言語の要件リストを提示しました。その後、DoD は競技会(より現代的な DARPA の「Grand Challenges」とよく似たもの)を支援しました。赤、緑、青、黄の 4 つの言語設計チームが競い合いました。緑チームが勝利し、MIL-STD-1815-A 言語仕様を作成しました。彼らの言語は、先駆的なプログラマーである Ada Lovelace にちなんで「Ada」と名付けられました。1815 は Lovelace の生年でした。 ↩
-
Ada ランタイムライブラリおよびコンパイラの認証エビデンス。AdaCore(2022 年アクセス)。 ↩
-
SPARK について。AdaCore(2022 年アクセス)。 ↩
-
Ada と SPARK における安全な動的メモリ管理。Maroua Maalej、Tucker Taft、Yannick Moy(2021)。 ↩
-
dictobject.c。CPython Interpreter(2021)。 ↩
-
あまりにも多すぎるリンクリストで Rust を学ぶ。Aria Desires(2021)。 ↩