Safety comments
Using unsafe blocks is often required in the Rust compiler or standard
library, but this is not done without rules: each unsafe block should have
a SAFETY: comment explaining why the block is safe, which invariants are
used and must be respected. Below are some examples taken from the standard
library:
Inside unsafe elements
This one shows how an unsafe function can pass the requirements through to its
caller with the use of documentation in a # Safety section while still having
more invariants needed that are not required from callers. clippy has a
lint for # Safety sections by the way.
/// Converts a mutable string slice to a mutable byte slice.
///
/// # Safety
///
/// The caller must ensure that the content of the slice is valid UTF-8
/// before the borrow ends and the underlying `str` is used.
///
/// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
///
/// ...
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
// SAFETY: the cast from `&str` to `&[u8]` is safe since `str`
// has the same layout as `&[u8]` (only libstd can make this guarantee).
// The pointer dereference is safe since it comes from a mutable reference which
// is guaranteed to be valid for writes.
unsafe { &mut *(self as *mut str as *mut [u8]) }
}
This example is for a function but the same principle applies to unsafe traits
like Send or Sync for example, though they have no # Safety section
since their entire documentation is about why they are unsafe.
Note that in the Rust standard library, unsafe_op_in_unsafe_fn is active
and so each unsafe operation in an unsafe function must be enclosed in an
unsafe block. This makes it easier to review such functions and to document
their unsafe parts.
Inside safe elements
Inside safe elements, a SAFETY: comment must not depend on anything from the
caller beside properly constructed types and values (i.e, if your function
receives a reference that is unaligned or null, it is the caller fault if it
fails and not yours).
SAFETY comments in safe elements often rely on checks that are done before
the unsafe block or on type invariants, like a division by NonZeroU8 would
not check for 0 before dividing.
pub fn split_at(&self, mid: usize) -> (&str, &str) {
// is_char_boundary checks that the index is in [0, .len()]
if self.is_char_boundary(mid) {
// SAFETY: just checked that `mid` is on a char boundary.
unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) }
} else {
slice_error_fail(self, 0, mid)
}
}