10 releases
0.3.2 | Oct 28, 2020 |
---|---|
0.3.1 | Oct 27, 2020 |
0.2.1 | Mar 2, 2020 |
0.2.0 | Aug 30, 2019 |
0.1.2 | Aug 21, 2019 |
#28 in #fn
1,852 downloads per month
18KB
302 lines
#[require_unsafe_in_body]
A procedural macro attribute to make an unsafe fn
still require unsafe
blocks in its body.
Motivation
Imagine having a function with a narrow contract, i.e., a function that can
trigger Undefined Behavior in some situations (incorrect inputs or call
context).
Rust safety design requires that this function be annotated unsafe
if it is
part of a public API, and even when it is not, it is highly advisable to do
so (code will be easier to maintain):
/// Swaps two values of a mutable slice, without checking that the indices are valid.
pub
unsafe // narrow contract
fn swap_indices_unchecked<T> (slice: &'_ mut [T], i: usize, j: usize)
{
let at_i: *mut T = slice.get_unchecked_mut(i);
let at_j: *mut T = slice.get_unchecked_mut(j);
::core::ptr::swap_nonoverlapping(at_i, at_j, 1)
}
As you can see, when a function is annotated unsafe
, the body of the function
no longer requires special unsafe
blocks around the most subtle things.
For instance, in this case, it may not be obvious that there are two distinct
unsafe
things happening:
-
we are performing unchecked indexing, which would break if
i ≥ len
orj ≥ len
; -
we are asserting that
at_i
andat_j
do not alias, which would break ifi = j
.
Since misusing any of these invariants is wildly unsound, it would be better if we could explicit where each invariant is or may be used:
/// Swaps two values of a mutable slice, without checking that the indices are valid.
///
/// # Safety
///
/// The indices must be valid:
///
/// - `i ≠ j`
///
/// - `i < slice.len()`
///
/// - `j < slice.len()`
#[allow(unused_unsafe)]
pub
unsafe // narrow contract
fn swap_indices_unchecked<T> (slice: &'_ mut [T], i: usize, j: usize)
{
let at_i: *mut T = unsafe {
// Safety: `i < slice.len()`
debug_assert!(i < slice.len());
slice.get_unchecked_mut(i)
};
let at_j: *mut T = unsafe {
// Safety: `j < slice.len()`
debug_assert!(j < slice.len());
slice.get_unchecked_mut(j)
};
unsafe {
// Safety: `at_i` and `at_j` do not alias since `i ≠ j`
debug_assert_ne!(i, j);
::core::ptr::swap_nonoverlapping(at_i, at_j, 1);
}
}
Sadly, since these unsafe
blocks are not required, not only do they trigger
unused_unsafe
warnings, they can also be mistakenly missed without Rust
complaining whatsoever.
That's what #[
require_unsafe_in_body
]
solves:
#[
require_unsafe_in_body
]
"automagically removes" the intrinsicunsafe
-ness (hygiene) of anunsafe fn
body, thus making it necessary to useunsafe
scopes inside.
Example
The code
#[macro_use]
extern crate require_unsafe_in_body;
/// Swaps two values of a mutable slice, without checking that the indices are valid.
#[require_unsafe_in_body]
pub
unsafe // narrow contract
fn swap_indices_unchecked<T> (slice: &'_ mut [T], i: usize, j: usize)
{
let at_i: *mut T = slice.get_unchecked_mut(i);
let at_j: *mut T = slice.get_unchecked_mut(j);
::core::ptr::swap_nonoverlapping(at_i, at_j, 1);
}
yields the following compiler error:
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> example.rs:11:24
|
11 | let at_i: *mut T = slice.get_unchecked_mut(i);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> example.rs:12:24
|
12 | let at_j: *mut T = slice.get_unchecked_mut(j);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> example.rs:13:5
|
13 | ::core::ptr::swap_nonoverlapping(at_i, at_j, 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
For more information about this error, try `rustc --explain E0133`.
Usage
-
Add this to your
Cargo.toml
:[dependencies] require_unsafe_in_body = "0.2.0"
-
Add this to your
lib.rs
(ormain.rs
):#[macro_use] extern crate require_unsafe_in_body;
-
You can then decorate:
-
functions, with the
#[
require_unsafe_in_body
]
attribute; -
methods, with the
#[
require_unsafe_in_bodies
]
attribute applied to the enclosingimpl
ortrait
block.
-
Dependencies
~1.5MB
~37K SLoC