Skip to content

Commit

Permalink
rust: init: add assert_pinned macro
Browse files Browse the repository at this point in the history
Add a macro to statically check if a field of a struct is marked with
`#[pin]` ie that it is structurally pinned. This can be used when
`unsafe` code needs to rely on fields being structurally pinned.

The macro has a special "inline" mode for the case where the type
depends on generic parameters from the surrounding scope.

Signed-off-by: Benno Lossin <[email protected]>
Co-developed-by: Alice Ryhl <[email protected]>
Signed-off-by: Alice Ryhl <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
[ Replaced `compile_fail` with `ignore` and a TODO note. Removed
  `pub` from example to clean `unreachable_pub` lint. - Miguel ]
Signed-off-by: Miguel Ojeda <[email protected]>
Signed-off-by: Paolo Bonzini <[email protected]>
(cherry picked from commit 0528ca0a4f858da3369d405af8c76b8248dfeb7b)
  • Loading branch information
Benno Lossin authored and y86-dev committed Nov 22, 2024
1 parent 6841b61 commit 1769d7f
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/__internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,32 @@ impl OnlyCallFromDrop {
Self(())
}
}

/// Initializer that always fails.
///
/// Used by [`assert_pinned!`].
///
/// [`assert_pinned!`]: crate::assert_pinned
pub struct AlwaysFail<T: ?Sized> {
_t: PhantomData<T>,
}

impl<T: ?Sized> AlwaysFail<T> {
/// Creates a new initializer that always fails.
pub fn new() -> Self {
Self { _t: PhantomData }
}
}

impl<T: ?Sized> Default for AlwaysFail<T> {
fn default() -> Self {
Self::new()
}
}

// SAFETY: `__pinned_init` always fails, which is always okay.
unsafe impl<T: ?Sized> PinInit<T, ()> for AlwaysFail<T> {
unsafe fn __pinned_init(self, _slot: *mut T) -> Result<(), ()> {
Err(())
}
}
69 changes: 69 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,75 @@ macro_rules! try_init {
};
}

/// Asserts that a field on a struct using `#[pin_data]` is marked with `#[pin]` ie. that it is
/// structurally pinned.
///
/// # Example
///
/// This will succeed:
/// ```
/// use pinned_init::*;
/// #[pin_data]
/// struct MyStruct {
/// #[pin]
/// some_field: u64,
/// }
///
/// assert_pinned!(MyStruct, some_field, u64);
/// ```
///
/// This will fail:
// TODO: replace with `compile_fail` when supported.
/// ```ignore
/// # use pinned_init::*;
/// #[pin_data]
/// struct MyStruct {
/// some_field: u64,
/// }
///
/// assert_pinned!(MyStruct, some_field, u64);
/// ```
///
/// Some uses of the macro may trigger the `can't use generic parameters from outer item` error. To
/// work around this, you may pass the `inline` parameter to the macro. The `inline` parameter can
/// only be used when the macro is invoked from a function body.
/// ```
/// # use pinned_init::*;
/// # use core::pin::Pin;
/// #[pin_data]
/// struct Foo<T> {
/// #[pin]
/// elem: T,
/// }
///
/// impl<T> Foo<T> {
/// fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
/// assert_pinned!(Foo<T>, elem, T, inline);
///
/// // SAFETY: The field is structurally pinned.
/// unsafe { self.map_unchecked_mut(|me| &mut me.elem) }
/// }
/// }
/// ```
#[macro_export]
macro_rules! assert_pinned {
($ty:ty, $field:ident, $field_ty:ty, inline) => {
let _ = move |ptr: *mut $field_ty| {
// SAFETY: This code is unreachable.
let data = unsafe { <$ty as $crate::__internal::HasPinData>::__pin_data() };
let init = $crate::__internal::AlwaysFail::<$field_ty>::new();
// SAFETY: This code is unreachable.
unsafe { data.$field(ptr, init) }.ok();
};
};

($ty:ty, $field:ident, $field_ty:ty) => {
const _: () = {
$crate::assert_pinned!($ty, $field, $field_ty, inline);
};
};
}

/// A pin-initializer for the type `T`.
///
/// To use this initializer, you will need a suitable memory location that can hold a `T`. This can
Expand Down

0 comments on commit 1769d7f

Please sign in to comment.