* [PATCH 1/3] rust: pin-init: internal: rework how `#[pin_data]` handles cfg
2026-07-23 18:19 [PATCH 0/3] rust: pin-init: upstream synchronization for 7.3 (round 2) Gary Guo
@ 2026-07-23 18:19 ` Gary Guo
2026-07-23 18:19 ` [PATCH 2/3] rust: pin-init: docs: link `Zeroable::zeroed` and `pin_init::zeroed` in documentation Gary Guo
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Gary Guo @ 2026-07-23 18:19 UTC (permalink / raw)
To: Benno Lossin, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
Cc: rust-for-linux, linux-kernel, Gary Guo
Attribute macros are invoked without cfg being resolved. This adds quite a
bit complexity to the macro because all of the macro needs to be careful to
attach necessary cfgs. This becomes especially tricky for tuple structs.
Thus, it is convenient if cfgs are all resolved like derive macros.
The most optimal way to handle this is via `TokenStream::expand_expr`, but
that is still unstable. We can also create an internal derive macro and
transform the attribute macro invocation to be derive macro, but doing
requires us to serialize all extracted information in a form of helper
attributes; it would also make it more difficult if we want to make changes
to the struct (which the self-reference feature would need).
Implement an approach where we generate two cfg-gated macro invocations
with cfg resolved within the invocation. This would mean when the loop
falls through, all field cfgs are resolved, so remove all handling of
cfg_attrs for the rest of the macro.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 82 ++++++++++++++++++++++++++--------
1 file changed, 64 insertions(+), 18 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 4438107682e0..3c9d9c7364e2 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use proc_macro2::TokenStream;
-use quote::{format_ident, quote};
+use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{End, Nothing, Parse},
parse_quote, parse_quote_spanned,
spanned::Spanned,
visit_mut::VisitMut,
- Attribute, Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause,
+ Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause,
};
use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
@@ -35,10 +35,18 @@ fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
}
}
+impl ToTokens for Args {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ Self::Nothing(_) => (),
+ Self::PinnedDrop(kw) => kw.to_tokens(tokens),
+ }
+ }
+}
+
struct FieldInfo<'a> {
field: &'a Field,
pinned: bool,
- cfg_attrs: Vec<&'a Attribute>,
}
pub(crate) fn pin_data(
@@ -68,6 +76,55 @@ pub(crate) fn pin_data(
}
};
+ // Handling cfg can gets very complicated, especially for tuple structs. Therefore, resolve all
+ // field cfgs first before continuing.
+ //
+ // We need to perform this after parsing so we can reliably detect field cfgs.
+ for (field_idx, field) in struct_.fields.iter_mut().enumerate() {
+ let cfg: Vec<_> = field
+ .attrs
+ .iter()
+ .filter(|a| a.path().is_ident("cfg"))
+ .map(|a| {
+ a.parse_args::<TokenStream>()
+ .expect("parse as token stream cannot fail")
+ })
+ .collect();
+
+ if cfg.is_empty() {
+ continue;
+ }
+
+ field.attrs.retain(|a| !a.path().is_ident("cfg"));
+ let cfg_true_struct = quote!(#struct_);
+
+ let punctuated = match &mut struct_.fields {
+ Fields::Named(fields) => &mut fields.named,
+ Fields::Unnamed(fields) => &mut fields.unnamed,
+ Fields::Unit => unreachable!(),
+ };
+ *punctuated = std::mem::take(punctuated)
+ .into_pairs()
+ .enumerate()
+ .filter(|&(i, _)| i != field_idx)
+ .map(|(_, p)| p)
+ .collect();
+ let cfg_false_struct = quote!(#struct_);
+
+ // Resolve one field at a time until we've got no more field cfgs.
+ //
+ // This is linear time because macro invocations with false cfg will not be expanded.
+ return Ok(quote!(
+ #[cfg(all(#(#cfg,)*))]
+ #[::pin_init::pin_data(#args)]
+ #cfg_true_struct
+
+ #[cfg(not(all(#(#cfg,)*)))]
+ #[::pin_init::pin_data(#args)]
+ #cfg_false_struct
+ ));
+ }
+
// The generics might contain the `Self` type. Since this macro will define a new type with the
// same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed
// to this struct definition. Therefore we have to replace `Self` with the concrete name.
@@ -90,16 +147,14 @@ pub(crate) fn pin_data(
dcx.error(&field, "#[pin] attribute specified more than once");
}
- let cfg_attrs = field
- .attrs
- .iter()
- .filter(|a| a.path().is_ident("cfg"))
- .collect();
+ assert!(
+ !field.attrs.iter().any(|a| a.path().is_ident("cfg")),
+ "cfgs should be all resolved at this point"
+ );
FieldInfo {
field: &*field,
pinned: pinned_count != 0,
- cfg_attrs,
}
})
.collect();
@@ -185,9 +240,7 @@ fn generate_unpin_impl(
let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| {
let ident = f.field.ident.as_ref().unwrap();
let ty = &f.field.ty;
- let cfg_attrs = &f.cfg_attrs;
quote!(
- #(#cfg_attrs)*
#ident: #ty
)
});
@@ -280,7 +333,6 @@ fn generate_projections(
.iter()
.map(|field| {
let Field { vis, ident, ty, .. } = &field.field;
- let cfg_attrs = &field.cfg_attrs;
let ident = ident
.as_ref()
@@ -288,11 +340,9 @@ fn generate_projections(
if field.pinned {
(
quote!(
- #(#cfg_attrs)*
#vis #ident: ::core::pin::Pin<&'__pin mut #ty>,
),
quote!(
- #(#cfg_attrs)*
// SAFETY: this field is structurally pinned.
#ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) },
),
@@ -300,11 +350,9 @@ fn generate_projections(
} else {
(
quote!(
- #(#cfg_attrs)*
#vis #ident: &'__pin mut #ty,
),
quote!(
- #(#cfg_attrs)*
#ident: &mut #this.#ident,
),
)
@@ -374,7 +422,6 @@ fn generate_the_pin_data(
.iter()
.map(|f| {
let Field { vis, ident, ty, .. } = f.field;
- let cfg_attrs = &f.cfg_attrs;
let field_name = ident
.as_ref()
@@ -391,7 +438,6 @@ fn generate_the_pin_data(
/// - `(*slot).#field_name` is properly aligned.
/// - `(*slot).#field_name` points to uninitialized and exclusively accessed
/// memory.
- #(#cfg_attrs)*
// Allow `non_snake_case` since the same warning will be emitted on
// the struct definition.
#[allow(non_snake_case)]
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 2/3] rust: pin-init: docs: link `Zeroable::zeroed` and `pin_init::zeroed` in documentation
2026-07-23 18:19 [PATCH 0/3] rust: pin-init: upstream synchronization for 7.3 (round 2) Gary Guo
2026-07-23 18:19 ` [PATCH 1/3] rust: pin-init: internal: rework how `#[pin_data]` handles cfg Gary Guo
@ 2026-07-23 18:19 ` Gary Guo
2026-07-23 18:19 ` [PATCH 3/3] rust: pin-init: mark `pin_init::zeroed` and `Zeroable::zeroed` as `#[inline]` Gary Guo
2026-07-27 11:31 ` [PATCH 0/3] rust: pin-init: upstream synchronization for 7.3 (round 2) Gary Guo
3 siblings, 0 replies; 5+ messages in thread
From: Gary Guo @ 2026-07-23 18:19 UTC (permalink / raw)
To: Benno Lossin, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
Cc: rust-for-linux, linux-kernel, Gary Guo, Nicolás Antinori
From: Nicolás Antinori <nico.antinori.7@gmail.com>
Modify the comments in the `pin_init::zeroed` and `Zeroable::zeroed`
functions to cross-reference each other and make developers aware of both
options.
This also adapts the example code in `Zeroable::zeroed` doc comments to use
that function.
Suggested-by: Miguel Ojeda <ojeda@kernel.org>
Link: https://lore.kernel.org/rust-for-linux/CANiq72kdCAyRUmXFcqQfkHpk1miG8Gagsn0_5U8p4WpKxv9d_g@mail.gmail.com/
Signed-off-by: Nicolás Antinori <nico.antinori.7@gmail.com>
[ Fix link. - Gary ]
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/src/lib.rs | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index ef9f20b11034..1f5005c110cd 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -1539,10 +1539,13 @@ fn init_zeroed() -> impl Init<Self>
/// Whenever a type implements [`Zeroable`], this function should be preferred over
/// [`core::mem::zeroed()`] or using `MaybeUninit<T>::zeroed().assume_init()`.
///
+ /// As const traits are not yet stable, [`pin_init::zeroed()`] can be used instead
+ /// when initialization is required in a `const` context.
+ ///
/// # Examples
///
/// ```
- /// use pin_init::{Zeroable, zeroed};
+ /// use pin_init::Zeroable;
///
/// #[derive(Zeroable)]
/// struct Point {
@@ -1550,7 +1553,7 @@ fn init_zeroed() -> impl Init<Self>
/// y: u32,
/// }
///
- /// let point: Point = zeroed();
+ /// let point: Point = Zeroable::zeroed();
/// assert_eq!(point.x, 0);
/// assert_eq!(point.y, 0);
/// ```
@@ -1582,6 +1585,9 @@ pub fn init_zeroed<T: Zeroable>() -> impl Init<T> {
/// Whenever a type implements [`Zeroable`], this function should be preferred over
/// [`core::mem::zeroed()`] or using `MaybeUninit<T>::zeroed().assume_init()`.
///
+/// While const traits remain unstable, this function serves as the `const` version of
+/// [`Zeroable::zeroed()`].
+///
/// # Examples
///
/// ```
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 3/3] rust: pin-init: mark `pin_init::zeroed` and `Zeroable::zeroed` as `#[inline]`
2026-07-23 18:19 [PATCH 0/3] rust: pin-init: upstream synchronization for 7.3 (round 2) Gary Guo
2026-07-23 18:19 ` [PATCH 1/3] rust: pin-init: internal: rework how `#[pin_data]` handles cfg Gary Guo
2026-07-23 18:19 ` [PATCH 2/3] rust: pin-init: docs: link `Zeroable::zeroed` and `pin_init::zeroed` in documentation Gary Guo
@ 2026-07-23 18:19 ` Gary Guo
2026-07-27 11:31 ` [PATCH 0/3] rust: pin-init: upstream synchronization for 7.3 (round 2) Gary Guo
3 siblings, 0 replies; 5+ messages in thread
From: Gary Guo @ 2026-07-23 18:19 UTC (permalink / raw)
To: Benno Lossin, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
Cc: rust-for-linux, linux-kernel, Gary Guo, Nicolás Antinori
From: Nicolás Antinori <nico.antinori.7@gmail.com>
The `pin_init::zeroed` function is a trivial wrapper around
`unsafe { core::mem::zeroed() }`, whereas `Zeroable::zeroed` is a trivial
wrapper around `pin_init::zeroed`. Mark them both as `#[inline]` to avoid
generating unnecessary symbols for them.
Signed-off-by: Nicolás Antinori <nico.antinori.7@gmail.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/src/lib.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index 1f5005c110cd..f4ccb0e87200 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -1557,6 +1557,7 @@ fn init_zeroed() -> impl Init<Self>
/// assert_eq!(point.x, 0);
/// assert_eq!(point.y, 0);
/// ```
+ #[inline]
fn zeroed() -> Self
where
Self: Sized,
@@ -1603,6 +1604,7 @@ pub fn init_zeroed<T: Zeroable>() -> impl Init<T> {
/// assert_eq!(point.x, 0);
/// assert_eq!(point.y, 0);
/// ```
+#[inline]
pub const fn zeroed<T: Zeroable>() -> T {
// SAFETY:By the type invariants of `Zeroable`, all zeroes is a valid bit pattern for `T`.
unsafe { core::mem::zeroed() }
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread