Skip to content

Commit 1769d7f

Browse files
committed
rust: init: add assert_pinned macro
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 <benno.lossin@proton.me> Co-developed-by: Alice Ryhl <aliceryhl@google.com> Signed-off-by: Alice Ryhl <aliceryhl@google.com> Link: https://lore.kernel.org/r/20240814-linked-list-v5-1-f5f5e8075da0@google.com [ Replaced `compile_fail` with `ignore` and a TODO note. Removed `pub` from example to clean `unreachable_pub` lint. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> (cherry picked from commit 0528ca0a4f858da3369d405af8c76b8248dfeb7b)
1 parent 6841b61 commit 1769d7f

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/__internal.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,32 @@ impl OnlyCallFromDrop {
261261
Self(())
262262
}
263263
}
264+
265+
/// Initializer that always fails.
266+
///
267+
/// Used by [`assert_pinned!`].
268+
///
269+
/// [`assert_pinned!`]: crate::assert_pinned
270+
pub struct AlwaysFail<T: ?Sized> {
271+
_t: PhantomData<T>,
272+
}
273+
274+
impl<T: ?Sized> AlwaysFail<T> {
275+
/// Creates a new initializer that always fails.
276+
pub fn new() -> Self {
277+
Self { _t: PhantomData }
278+
}
279+
}
280+
281+
impl<T: ?Sized> Default for AlwaysFail<T> {
282+
fn default() -> Self {
283+
Self::new()
284+
}
285+
}
286+
287+
// SAFETY: `__pinned_init` always fails, which is always okay.
288+
unsafe impl<T: ?Sized> PinInit<T, ()> for AlwaysFail<T> {
289+
unsafe fn __pinned_init(self, _slot: *mut T) -> Result<(), ()> {
290+
Err(())
291+
}
292+
}

src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,75 @@ macro_rules! try_init {
745745
};
746746
}
747747

748+
/// Asserts that a field on a struct using `#[pin_data]` is marked with `#[pin]` ie. that it is
749+
/// structurally pinned.
750+
///
751+
/// # Example
752+
///
753+
/// This will succeed:
754+
/// ```
755+
/// use pinned_init::*;
756+
/// #[pin_data]
757+
/// struct MyStruct {
758+
/// #[pin]
759+
/// some_field: u64,
760+
/// }
761+
///
762+
/// assert_pinned!(MyStruct, some_field, u64);
763+
/// ```
764+
///
765+
/// This will fail:
766+
// TODO: replace with `compile_fail` when supported.
767+
/// ```ignore
768+
/// # use pinned_init::*;
769+
/// #[pin_data]
770+
/// struct MyStruct {
771+
/// some_field: u64,
772+
/// }
773+
///
774+
/// assert_pinned!(MyStruct, some_field, u64);
775+
/// ```
776+
///
777+
/// Some uses of the macro may trigger the `can't use generic parameters from outer item` error. To
778+
/// work around this, you may pass the `inline` parameter to the macro. The `inline` parameter can
779+
/// only be used when the macro is invoked from a function body.
780+
/// ```
781+
/// # use pinned_init::*;
782+
/// # use core::pin::Pin;
783+
/// #[pin_data]
784+
/// struct Foo<T> {
785+
/// #[pin]
786+
/// elem: T,
787+
/// }
788+
///
789+
/// impl<T> Foo<T> {
790+
/// fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
791+
/// assert_pinned!(Foo<T>, elem, T, inline);
792+
///
793+
/// // SAFETY: The field is structurally pinned.
794+
/// unsafe { self.map_unchecked_mut(|me| &mut me.elem) }
795+
/// }
796+
/// }
797+
/// ```
798+
#[macro_export]
799+
macro_rules! assert_pinned {
800+
($ty:ty, $field:ident, $field_ty:ty, inline) => {
801+
let _ = move |ptr: *mut $field_ty| {
802+
// SAFETY: This code is unreachable.
803+
let data = unsafe { <$ty as $crate::__internal::HasPinData>::__pin_data() };
804+
let init = $crate::__internal::AlwaysFail::<$field_ty>::new();
805+
// SAFETY: This code is unreachable.
806+
unsafe { data.$field(ptr, init) }.ok();
807+
};
808+
};
809+
810+
($ty:ty, $field:ident, $field_ty:ty) => {
811+
const _: () = {
812+
$crate::assert_pinned!($ty, $field, $field_ty, inline);
813+
};
814+
};
815+
}
816+
748817
/// A pin-initializer for the type `T`.
749818
///
750819
/// To use this initializer, you will need a suitable memory location that can hold a `T`. This can

0 commit comments

Comments
 (0)