unsafe
is a dangerous but sometimes necessary escape hatch in Rust.
When writing or reviewing unsafe
code, it's essential that you:
- clearly identify all of the assumptions and invariants required by every
unsafe
block; - ensure that those assumptions are met;
- ensure that those assumptions will continue to be met.
In order to ensure that unsafe
invariants are not broken by future editors,
each usage of unsafe
must be accompanied by a clear, concise comment
explaining what assumptions are being made.
Where possible, package up unsafety into a single function or module that provides a safe abstraction to the outside world. FFI calls should usually be exposed through a safe function whose only purpose is to provide a safe wrapper around the function in question. These functions should contain a comment with the following information (if applicable):
- Preconditions (e.g. what are the valid states of the arguments?)
- Failure handling (e.g. what values should be free'd? forgotten? invalidated?)
- Success handling (e.g. what values are created or consumed?)
Example:
impl Channel {
/// Write a message to a channel. Wraps the
/// [zx_channel_write](//zircon/syscalls/channel_write.md)
/// syscall.
pub fn write(&self, bytes: &[u8], handles: &mut Vec<Handle>)
-> Result<(), Status>
{
let opts = 0;
let n_bytes = try!(usize_into_u32(bytes.len()).map_err(|_| Status::OUT_OF_RANGE));
let n_handles = try!(usize_into_u32(handles.len()).map_err(|_| Status::OUT_OF_RANGE));
// Requires that `self` contains a currently valid handle or ZX_HANDLE_INVALID.
// On success, all of the handles in the handles array have been moved.
// They must be forgotten and not dropped.
// On error, all handles are still owned by the current process and can be dropped.
unsafe {
let status = sys::zx_channel_write(self.raw_handle(), opts, bytes.as_ptr(), n_bytes,
handles.as_ptr() as *const sys::zx_handle_t, n_handles);
ok(status)?;
// Handles were successfully transferred, forget them on sender side
handles.set_len(0);
Ok(())
}
}
}
If unsafe
code relies on other safe code for correctness, a comment
must be left alongside the corresponding safe code indicating what invariants
it must uphold and why. Invariants that rely upon the behavior of multiple
functions will draw extra scrutiny, and cross-module or cross-crate unsafety
requires even more attention. unsafe
code that depends on correct behavior of
a third-party crate will likely be rejected, and unsafe
code that depends
upon the internal representation details of third-party types will never be
accepted.
Finally, struct
definitions containing unsafe
types such as *const
,
*mut
, or UnsafeCell
must include a comment explaining the internal
representation invariants of the type. If the unsafe
type is used to perform
a mutation OR if it aliases with memory inside another type, there should be
an explanation of how it upholds Rust's "aliasing XOR mutation" requirements.
If any derive
able traits are purposefully omitted for safety reasons, a
comment must be left to prevent future editors from adding the unsafe impls.
The rules above are applied to any additions of unsafe
code or any
modifications of existing unsafe
code.
For more discussion on encapsulating unsafe
invariants, see
Ralf Jung's "The Scope of Unsafe" and
Niko Matsakis's "Tootsie Pop" model.