Rustでunsafeが必要な操作

Rustでは unsafe の表明を行わない限り未定義動作が発生しない。コンパイラや言語仕様のミスにより未定義動作が発生しうる場合には優先的に修正される。(なお、整数オーバーフローのように特定条件下でエラーになるものであっても、正しくunwind/abortできるものは未定義動作とは呼ばない。)

unsafe の表明

unsafe の表明は、構文的には以下のいずれかである。

  • unsafe ブロック
  • unsafe impl

unsafe を表明した場合、その部分では「安全かどうかをコンパイラが保証できない操作」が行えるため、この部分が安全であるかどうかを検査するのはプログラマの責任であるということになる。それぞれの操作には、それが安全に実行できるための事前条件(safety precondition)が決められているから、これが成り立っていることをプログラマが自分で保証する、という算段である。

unsafe の要求

unsafe を用いたライブラリ関数の場合、「関数が何らかの性質を満たしているときは安全」という状況が考えられる。例えば、比較関数が正当であることを前提としたソートアルゴリズムの実装というのが考えられる。

Rustでは原則としてこれは許されない。ライブラリの呼び出し側が (unsafe だったり、可視性を破壊したりしていない範囲内で) どんな異常な引数で関数を呼んでも、その関数は安全に動作しないといけない。

どうしても呼び出し側に安全性の責任を転嫁したい場合は、関数シグネチャunsafe をつける。これにより、そのライブラリ関数は、安全に使うための追加の事前条件(safety precondition)を要求していることになる。その事前条件はドキュメントに説明されるべきということになる。

このように unsafe を要求する構文には以下のものがある。

なお、関数定義の unsafe は要求と表明を兼ねている。

unsafe ブロック/関数内でないとできない操作の一覧

unsafeが適切に呼ばれているかどうかは rustc::middle::effectモジュール で検査されている。

以下では事前条件も書いてみるが、すべて筆者による推測である。

unsafe 関数/unsafe メソッドの呼び出し

unsafe のついている関数やメソッドを呼び出したときに発生する。関数を取り出して呼び出していない場合は発生しない。

事前条件: その関数/メソッドによって指定されている事前条件を守る。

生ポインタの参照外し

*const T*mut T 型の値 p に対し、 *p を行う。 &*p のように左辺値の場合 (生ポインタを参照に昇格するのに使う) にも unsafe が必要である。

事前条件・不変条件: おそらく以下のような条件が必要とされている。

  • アラインメントが揃っている。
  • 有効な場所を指している。
  • 有効な値が格納されている。または、昇格した参照が使用されるまでに有効な値が格納される。
  • エイリアスを持たないか、これ自身を含む全てのエイリアスが読み取り専用として扱われている。

インラインアセンブリ

asm!()global_asm!() によるインラインアセンブリの埋め込みは常に unsafe である。

事前条件・不変条件: Rustのもつ全ての不変条件を守ること。例えば、書き込み借用できるメモリ以外に書き込まない。不正な値を書き込まない。 Copy でない値をコピーした場合に元の位置のデストラクタを呼んではいけない。など。

static mut 変数へのアクセス

static mut で宣言された静的変数の読み取り/書き込みアクセスは unsafe である。

事前条件・不変条件: 書き込み参照するときは、他に誰かが参照していないこと。読み取り参照するときは、他に誰かが書き込み参照していないこと。(他スレッドからのアクセスも含む)

extern { static } 変数へのアクセス

extern { static X : u32; } のように、FFIで外部の静的変数を参照する変数への読み取り/書き込みアクセスは unsafe である。

事前条件: 値を取り出す場合は、不正な値が入っていないよう注意する。

なお、この条件は互換性のために現在は警告扱いになっている(warning cycle)。将来はエラーとなる予定である。

union 要素へのアクセス

union 要素へのアクセス (読み取り、代入、パターンマッチによる読み取り) は unsafe である。

事前条件・不変条件:

  • 読み取りでは、そのフィールドに有効な値が入っていること。
  • 書き込みでは、この union の何らかの不変条件を保ち、結果的に Drop が正常に動作すること。 (Drop を実装していなければ問題ない)

なお、最近の変更により、 Copyunion の要素への代入は unsafe ではなくなった。