Unsafe

Rustの主たる魅力は、プログラムの動作についての強力で静的な保証です。 しかしながら、安全性検査は本来保守的なものです。 すなわち、実際には安全なのに、そのことがコンパイラには検証できないプログラムがいくらか存在します。 その類のプログラムを書くためには、制約を少し緩和するようコンパイラに対して伝えることが要ります。 そのために、Rustには unsafe というキーワードがあります。 unsafe を使ったコードは、普通のコードよりも制約が少なくなります。

まずシンタックスをみて、それからセマンティクスについて話しましょう。 unsafe は4つの場面で使われます。 1つめは、関数がアンセーフであることを印付ける場合です。

fn main() { unsafe fn danger_will_robinson() { // scary stuff // 恐ろしいもの } }
unsafe fn danger_will_robinson() {
    // 恐ろしいもの
}

たとえば、FFIから呼び出されるすべての関数はunsafeで印付けることが必要です。 unsafeの2つめの用途は、アンセーフブロックです。

fn main() { unsafe { // scary stuff // 恐ろしいもの } }
unsafe {
    // 恐ろしいもの
}

3つめは、アンセーフトレイトです。

fn main() { unsafe trait Scary { } }
unsafe trait Scary { }

そして、4つめは、そのアンセーフトレイトを実装する場合です。

fn main() { unsafe trait Scary { } unsafe impl Scary for i32 {} }
unsafe impl Scary for i32 {}

大きな問題を引き起こすバグがあるかもしれないコードを明示できるのは重要なことです。 もしRustのプログラムがセグメンテーション違反を起こしても、バグは unsafe で印付けられた区間のどこかにあると確信できます。

「安全」とはどういう意味か?

Rustの文脈で、安全とは「どのようなアンセーフなこともしない」ことを意味します。

訳注: 正確には、安全とは「決して未定義動作を起こさない」ということです。 そして、安全性が保証されていないことを「アンセーフ」と呼びます。 つまり、未定義動作が起きるおそれがあるなら、それはアンセーフです。

知っておくべき重要なことに、たいていのコードにおいて望ましくないが、アンセーフ ではない とされている動作がいくらか存在するということがあります。

Rustはソフトウェアが抱えるすべての種類の問題を防げるわけではありません。 Rustでバグのあるコードを書くことはできますし、実際に書かれるでしょう。 これらの動作は良いことではありませんが、特にアンセーフだとは見なされません。

さらに、Rustにおいては、次のものは未定義動作で、 unsafe コード中であっても、避ける必要があります。

訳注: 関数に付いているunsafeは「その関数の処理はアンセーフである」ということを表します。 その一方で、ブロックに付いているunsafeは「ブロック中の個々の操作はアンセーフだが、全体としては安全な処理である」ということを表します。 避ける必要があるのは、未定義動作が起こりうる処理をアンセーフブロックの中に書くことです。 それは、アンセーフブロックの処理が安全であるために、その内部で未定義動作が決して起こらないことが必要だからです。 アンセーフ関数には安全性の保証が要らないので、未定義動作が起こりうるアンセーフ関数を定義することに問題はありません。

アンセーフの能力

アンセーフ関数・アンセーフブロックでは、Rustは普段できない3つのことをさせてくれます。たった3つです。それは、

  1. 静的ミュータブル変数のアクセスとアップデート。
  2. 生ポインタの参照外し。
  3. アンセーフ関数の呼び出し。これが最も強力な能力です。

以上です。 重要なのは、 unsafe が、たとえば「借用チェッカをオフにする」といったことを行わないことです。 Rustのコードの適当な位置に unsafe を加えてもセマンティクスは変わらず、何でもただ受理するようになるということにはなりません。 それでも、unsafe はルールのいくつかを破るコードを書けるようにはするのです。

また、unsafe キーワードは、Rust以外の言語とのインターフェースを書くときに遭遇するでしょう。 ライブラリの提供するメソッドの周りに、安全な、Rustネイティブのインターフェースを書くことが推奨されています。

これから、その基本的な3つの能力を順番に見ていきましょう。

static mut のアクセスとアップデート。

Rustには「static mut」という、ミュータブルでグローバルな状態を実現する機能があります。 これを使うことはデータレースが起こるおそれがあるので、本質的に安全ではありません。 詳細は、この本のstaticセクションを参照してください。

生ポインタの参照外し

生ポインタによって任意のポインタ演算が可能になりますが、いくつもの異なるメモリ安全とセキュリティの問題が起こるおそれがあります。 ある意味で、任意のポインタを参照外しする能力は行いうる操作のうち最も危険なもののひとつです。 詳細は、この本の生ポインタに関するセクションを参照してください。

アンセーフ関数の呼び出し

この最後の能力は、unsafeの両面とともに働きます。 すなわち、unsafeで印付けられた関数は、アンセーフブロックの内部からのみ呼び出すことができます。

この能力は強力で多彩です。 Rustはいくらかのcompiler intrinsicsをアンセーフ関数として公開しており、また、いくつかのアンセーフ関数は安全性検査を回避することで、安全性とスピードを引き換えています。

繰り返しになりますが、アンセーフブロックと関数の内部で任意のことが できる としても、それをすべきだということを意味しません。コンパイラは、あなたが不変量を守っているかのように動作しますから、注意してください!