#unsafe #body #safety #fn #proc-macro #hygiene

macro require_unsafe_in_body

Make unsafe fn still require unsafe blocks in the function’s body

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

#23 in #fn

Download history 2816/week @ 2023-11-26 2478/week @ 2023-12-03 2506/week @ 2023-12-10 1799/week @ 2023-12-17 502/week @ 2023-12-24 980/week @ 2023-12-31 1503/week @ 2024-01-07 1922/week @ 2024-01-14 2316/week @ 2024-01-21 1964/week @ 2024-01-28 950/week @ 2024-02-04 1815/week @ 2024-02-11 1938/week @ 2024-02-18 1595/week @ 2024-02-25 1878/week @ 2024-03-03 685/week @ 2024-03-10

6,114 downloads per month

MIT license

18KB
302 lines

#[require_unsafe_in_body]

A procedural macro attribute to make an unsafe fn still require unsafe blocks in its body.

Latest version Documentation License

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 or j ≥ len;

  • we are asserting that at_i and at_j do not alias, which would break if i = 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 intrinsic unsafe-ness (hygiene) of an unsafe fn body, thus making it necessary to use unsafe 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

  1. Add this to your Cargo.toml:

    [dependencies]
    require_unsafe_in_body = "0.2.0"
    
  2. Add this to your lib.rs (or main.rs):

    #[macro_use]
    extern crate require_unsafe_in_body;
    
  3. You can then decorate:

Dependencies

~1.5MB
~33K SLoC