安全と危険のご紹介
安全な「高級」言語のプログラマは、本質的なジレンマに直面します。何が欲しいかをただ伝えるだけで、 それがどのように実現されるかを悩む必要がないのは 本当に 素晴らしいのですが、それが許容できないほどの ひどいパフォーマンスをもたらすこともあります。 期待するパフォーマンスを得るために、明瞭で慣用的なやり方を断念しなくてはいけないかもしれないし、 または、どうしようもないと諦めて、それほど心地よくない 危険な 言語で実装することを決心するかもしれません。
もっと悪いことに、オペレーティングシステムと直接話したい時には、C 言語 という危険な言語で 会話を しなくてはなりません。C 言語はつねに存在し、逃れることはできないのです。 C 言語はプログラミングの世界での橋渡し言語だからです。 他の安全な言語も、たいてい C 言語のインターフェースを世界中に野放しでさらしています。 理由の如何にかかわらず、あなたのプログラムが C 言語と会話した瞬間に、安全ではなくなるのです。
とはいえ、Rust は 完全に 安全なプログラミング言語です。
・・・いえ、Rust は安全なプログラミング言語をもっていると言えます。一歩下がって考えてみましょう。
Rust は 2 つのプログラミング言語から成り立っていると言えます。安全な Rust と 危険な Rust です。 安全な Rust は、本当に全く安全ですが、危険な Rust は、当然ですが、本当に全く安全ではありません。 実際、危険な Rust では本当に本当に危険な事ができるのです。
安全な Rust は真の Rust プログラミング言語です。もしあなたが安全な Rust だけでコードを書くなら、 型安全やメモリ安全性などを心配する必要はないでしょう。 ヌルポインタやダングリングポインタ、馬鹿げた「未定義な挙動」などに我慢する必要はないのです。
なんて素晴らしいんだ。
また、標準ライブラリにはすぐに使える十分なユーティリティが揃っています。 それを使えば、ハイパフォーマンスでかっこいいアプリケーションやライブラリを、 正当で慣用的な安全な Rust で書けるでしょう。
でも、もしかしたらあなたは他の言語と話したいかもしれません。もしかしたら標準ライブラリが提供していない 低レイヤを抽象化しようとしているのかもしれないし、もしかしたら標準ライブラリを書いている (標準ライブラリは Rust で書かれています)のかもしれないし、もしかしたらあなたがやりたい事は、 型システムが理解できない、ぎょっとするようなことかもしれません。 もしかしたらあなたには危険な Rust が必要かもしれません。
危険な Rust のルールとセマンティクスは、安全な Rust と同じです。 ただし、危険な Rust ではちょっと多くの事ができ、それは間違いなく安全ではありません。
危険な Rust であなたができる事は、たったこれだけです。
- 生ポインタが指す値を得る
unsafe
な関数を呼ぶ(C 言語で書かれた関数や、intrinsic、生のアロケータなど)unsafe
なトレイトを実装する- 静的な構造体を変更する
これだけです。これらの操作がなぜ「危険」と分類されているかというと、 間違って使うととても恐ろしい「未定義な挙動」を引き起こすからです。 「未定義な挙動」が起きると、コンパイラは、あなたのプログラムにとってどんな悪いことでもできるようになります。 何があっても「未定義な挙動」を起こすべきではないです。
C 言語と違って、Rust では「未定義な挙動」は限定されています。 言語コアは次のような事が起きるのを防ぎます。
- ヌルポインタやダングリングポインタの参照外し
- 未初期化のメモリ を読む
- ポインタエイリアスルール を破る
- 不正なプリミティブな値を生成する
- ダングリング参照、ヌル参照
- 0 でも 1 でもない
bool
値 - 未定義な
enum
判別式 - [0x0, 0xD7FF] と [0xE000, 0x10FFFF] 範囲外の
char
値 - utf8 ではない
str
値
- 他の言語に巻き戻す
- データ競合 を引き起こす
これだけです。これが、Rust が防ぐ「未定義な挙動」の原因です。 もちろん、危険な関数やトレイトが「未定義な挙動」を起こさないための他の制約を作り出す事は可能ですが、 そういった制約が破られた場合、たいてい上の問題のどれかを引き起こします。 コンパイラ intrinsic がその他の制約を生み出し、コードの最適化に関する特別な仮定をすることもあります。
Rust はその他の疑わしい操作については、とても寛容です。 Rust は次の操作を「安全」だと判断します。
- デッドロック
- 競合状態
- メモリリーク
- デストラクタを呼ぶことに失敗する
- 整数のオーバーフロー
- プログラムの異常終了
- 本番環境のデータベースを削除してしまう事
とはいえ、こういうことをできてしまうプログラムは恐らく間違っていると言えるでしょう。 Rust はこういった事を起きにくくするためのツールをたくさん提供します。 しかし、これらの問題を完全に防ぐのは現実的ではないと考えられています。