rust: pin-init: add MaybeZeroable derive macro

This derive macro implements `Zeroable` for structs & unions precisely
if all fields also implement `Zeroable` and does nothing otherwise. The
plain `Zeroable` derive macro instead errors when it cannot derive
`Zeroable` safely. The `MaybeZeroable` derive macro is useful in cases
where manual checking is infeasible such as with the bindings crate.

Move the zeroable generics parsing into a standalone function in order
to avoid code duplication between the two derive macros.

Link: 1165cdad1a
Signed-off-by: Benno Lossin <benno.lossin@proton.me>
This commit is contained in:
Benno Lossin 2025-04-21 22:18:52 +00:00
parent a313d41a2b
commit 00fccd3ecc
4 changed files with 120 additions and 1 deletions

View file

@ -47,3 +47,8 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
zeroable::derive(input.into()).into()
}
#[proc_macro_derive(MaybeZeroable)]
pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream {
zeroable::maybe_derive(input.into()).into()
}

View file

@ -6,7 +6,14 @@ use proc_macro2 as proc_macro;
use crate::helpers::{parse_generics, Generics};
use proc_macro::{TokenStream, TokenTree};
pub(crate) fn derive(input: TokenStream) -> TokenStream {
pub(crate) fn parse_zeroable_derive_input(
input: TokenStream,
) -> (
Vec<TokenTree>,
Vec<TokenTree>,
Vec<TokenTree>,
Option<TokenTree>,
) {
let (
Generics {
impl_generics,
@ -64,6 +71,11 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
if in_generic && !inserted {
new_impl_generics.extend(quote! { : ::pin_init::Zeroable });
}
(rest, new_impl_generics, ty_generics, last)
}
pub(crate) fn derive(input: TokenStream) -> TokenStream {
let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input);
quote! {
::pin_init::__derive_zeroable!(
parse_input:
@ -74,3 +86,16 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
);
}
}
pub(crate) fn maybe_derive(input: TokenStream) -> TokenStream {
let (rest, new_impl_generics, ty_generics, last) = parse_zeroable_derive_input(input);
quote! {
::pin_init::__maybe_derive_zeroable!(
parse_input:
@sig(#(#rest)*),
@impl_generics(#(#new_impl_generics)*),
@ty_generics(#(#ty_generics)*),
@body(#last),
);
}
}

View file

@ -413,6 +413,36 @@ pub use ::pin_init_internal::pinned_drop;
/// ```
pub use ::pin_init_internal::Zeroable;
/// Derives the [`Zeroable`] trait for the given struct if all fields implement [`Zeroable`].
///
/// Contrary to the derive macro named [`macro@Zeroable`], this one silently fails when a field
/// doesn't implement [`Zeroable`].
///
/// # Examples
///
/// ```
/// use pin_init::MaybeZeroable;
///
/// // implmements `Zeroable`
/// #[derive(MaybeZeroable)]
/// pub struct DriverData {
/// id: i64,
/// buf_ptr: *mut u8,
/// len: usize,
/// }
///
/// // does not implmement `Zeroable`
/// #[derive(MaybeZeroable)]
/// pub struct DriverData2 {
/// id: i64,
/// buf_ptr: *mut u8,
/// len: usize,
/// // this field doesn't implement `Zeroable`
/// other_data: &'static i32,
/// }
/// ```
pub use ::pin_init_internal::MaybeZeroable;
/// Initialize and pin a type directly on the stack.
///
/// # Examples

View file

@ -1443,3 +1443,62 @@ macro_rules! __derive_zeroable {
};
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __maybe_derive_zeroable {
(parse_input:
@sig(
$(#[$($struct_attr:tt)*])*
$vis:vis struct $name:ident
$(where $($whr:tt)*)?
),
@impl_generics($($impl_generics:tt)*),
@ty_generics($($ty_generics:tt)*),
@body({
$(
$(#[$($field_attr:tt)*])*
$field_vis:vis $field:ident : $field_ty:ty
),* $(,)?
}),
) => {
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
#[automatically_derived]
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
where
$(
// the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds`
// feature <https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956>.
$field_ty: for<'__dummy> $crate::Zeroable,
)*
$($($whr)*)?
{}
};
(parse_input:
@sig(
$(#[$($struct_attr:tt)*])*
$vis:vis union $name:ident
$(where $($whr:tt)*)?
),
@impl_generics($($impl_generics:tt)*),
@ty_generics($($ty_generics:tt)*),
@body({
$(
$(#[$($field_attr:tt)*])*
$field_vis:vis $field:ident : $field_ty:ty
),* $(,)?
}),
) => {
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
#[automatically_derived]
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
where
$(
// the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds`
// feature <https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956>.
$field_ty: for<'__dummy> $crate::Zeroable,
)*
$($($whr)*)?
{}
};
}