Safety コメント
Rust コンパイラや標準ライブラリでは unsafe ブロックの使用がしばしば必要になりますが、これはルールなしに行われるわけではありません。各 unsafe ブロックには、そのブロックがなぜ安全なのか、どの不変条件が使用され、尊重されなければならないのかを説明する SAFETY: コメントがあるべきです。以下は標準ライブラリから取ったいくつかの例です。
unsafe 要素の内部
この例は、unsafe 関数が # Safety セクションのドキュメントを使用して要件を呼び出し元に伝えつつ、呼び出し元には要求されない追加の不変条件を内部で必要とする方法を示しています。ちなみに、clippy には # Safety セクション用の lint があります。
/// ミュータブルな文字列スライスをミュータブルなバイトスライスに変換します。
///
/// # Safety
///
/// 呼び出し元は、借用が終了し、基になる `str` が使用される前に、
/// スライスの内容が有効な UTF-8 であることを保証しなければなりません。
///
/// 内容が有効な UTF-8 ではない `str` の使用は未定義動作です。
///
/// ...
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
// SAFETY: `&str` から `&[u8]` へのキャストは安全です。なぜなら `str` は
// `&[u8]` と同じレイアウトを持つためです(この保証を行えるのは libstd のみです)。
// ポインターのデリファレンスは安全です。なぜなら、書き込みに対して有効であることが
// 保証されているミュータブル参照に由来するためです。
unsafe { &mut *(self as *mut str as *mut [u8]) }
}
この例は関数に関するものですが、同じ原則はたとえば Send や Sync のような unsafe trait にも適用されます。ただし、それらのドキュメント全体がなぜ unsafe であるかに関するものなので、# Safety セクションはありません。
Rust 標準ライブラリでは unsafe_op_in_unsafe_fn が有効になっているため、unsafe 関数内の各 unsafe 操作は unsafe ブロックで囲まれなければならないことに注意してください。これにより、そのような関数のレビューや、その unsafe な部分の文書化が容易になります。
safe 要素の内部
safe な要素の内部では、SAFETY: コメントは、適切に構築された型と値以外について、呼び出し元に依存してはなりません(つまり、あなたの関数がアラインメントされていない参照や null の参照を受け取った場合、それが失敗するなら呼び出し元の責任であり、あなたの責任ではありません)。
safe な要素内の SAFETY コメントは、unsafe ブロックの前に行われるチェックや型の不変条件に依存することが多いです。たとえば、NonZeroU8 による除算では、除算前に 0 かどうかをチェックしないような場合です。
pub fn split_at(&self, mid: usize) -> (&str, &str) {
// is_char_boundary は、インデックスが [0, .len()] の範囲内にあることをチェックします
if self.is_char_boundary(mid) {
// SAFETY: `mid` が文字境界上にあることを確認済みです。
unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) }
} else {
slice_error_fail(self, 0, mid)
}
}