[PATCH 2/4] rust: pin-init: internal: pin_data: support tuple struct projections
From: Gary Guo
Date: Fri Sep 04 2026 - 11:27:08 EST
From: Mohamad Alsadhan <mo@xxxxxxx>
`#[pin_data]` rejects tuple structs because it assumes every field has a
name, which it uses for the projection field, the `__Unpin` field and the
pin-data accessor.
Identify fields by `syn::Member` instead, so that tuple fields are referred
to by their index in generated field accesses. The names that generated
items still need are derived from the index as `_0`, `_1`, etc.
The projection of a tuple struct is a tuple struct itself, so projected
fields are accessed with the same `.0`, `.1` syntax as on the input
type rather than through synthesised names.
Signed-off-by: Mohamad Alsadhan <mo@xxxxxxx>
[ Moved utility code to util.rs as extension trait - Gary ]
Signed-off-by: Gary Guo <gary@xxxxxxxxxxx>
---
rust/pin-init/internal/src/pin_data.rs | 112 +++++++++++++++++++++++----------
rust/pin-init/internal/src/util.rs | 31 ++++++++-
rust/pin-init/src/lib.rs | 23 +++++++
3 files changed, 130 insertions(+), 36 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 074bc6b3091a..8cd9bf139567 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -7,7 +7,8 @@
parse_quote, parse_quote_spanned,
spanned::Spanned,
visit_mut::VisitMut,
- Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause,
+ Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility,
+ WhereClause,
};
use crate::{
@@ -49,6 +50,7 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
struct FieldInfo<'a> {
field: &'a Field,
+ member: Member,
pinned: bool,
}
@@ -129,10 +131,12 @@ pub(crate) fn pin_data(
replacer.visit_generics_mut(&mut struct_.generics);
replacer.visit_fields_mut(&mut struct_.fields);
+ let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_));
let fields: Vec<FieldInfo<'_>> = struct_
.fields
.iter_mut()
- .map(|field| {
+ .enumerate()
+ .map(|(index, field)| {
let len = field.attrs.len();
field.attrs.retain(|a| !a.path().is_ident("pin"));
let pinned_count = len - field.attrs.len();
@@ -144,23 +148,30 @@ pub(crate) fn pin_data(
!field.attrs.iter().any(|a| a.path().is_ident("cfg")),
"cfgs should be all resolved at this point"
);
+ let member = match &field.ident {
+ Some(ident) => Member::Named(ident.clone()),
+ None => Member::Unnamed(Index {
+ index: index as u32,
+ span: field.span(),
+ }),
+ };
FieldInfo {
field: &*field,
+ member,
pinned: pinned_count != 0,
}
})
.collect();
for field in &fields {
- let ident = field.field.ident.as_ref().unwrap();
-
if !field.pinned && is_phantom_pinned(&field.field.ty) {
dcx.warn(
field.field,
format!(
- "The field `{ident}` of type `PhantomPinned` only has an effect \
+ "The field {} of type `PhantomPinned` only has an effect \
if it has the `#[pin]` attribute",
+ field.member.display_name(),
),
);
}
@@ -168,8 +179,13 @@ pub(crate) fn pin_data(
let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields);
let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args);
- let projections =
- generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields);
+ let projections = generate_projections(
+ &struct_.vis,
+ &struct_.ident,
+ &struct_.generics,
+ is_tuple_struct,
+ &fields,
+ );
let the_pin_data =
generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields);
@@ -231,7 +247,7 @@ fn generate_unpin_impl(
unreachable!()
};
let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| {
- let ident = f.field.ident.as_ref().unwrap();
+ let ident = f.member.as_ident();
let ty = &f.field.ty;
quote!(
#ident: #ty
@@ -313,6 +329,7 @@ fn generate_projections(
vis: &Visibility,
ident: &Ident,
generics: &Generics,
+ is_tuple_struct: bool,
fields: &[FieldInfo<'_>],
) -> TokenStream {
let (impl_generics, ty_generics, _) = generics.split_for_impl();
@@ -325,28 +342,32 @@ fn generate_projections(
let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields
.iter()
.map(|field| {
- let Field { vis, ident, ty, .. } = &field.field;
+ let Field { vis, ty, .. } = &field.field;
+ let member = &field.member;
+ // The projection of a tuple struct is a tuple struct itself, so its fields are
+ // positional and must not be named.
+ let name = (!is_tuple_struct).then(|| {
+ let ident = field.member.as_ident();
+ quote!(#ident:)
+ });
- let ident = ident
- .as_ref()
- .expect("only structs with named fields are supported");
if field.pinned {
(
quote!(
- #vis #ident: ::core::pin::Pin<&'__pin mut #ty>,
+ #vis #name ::core::pin::Pin<&'__pin mut #ty>,
),
quote!(
// SAFETY: this field is structurally pinned.
- #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) },
+ #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) },
),
)
} else {
(
quote!(
- #vis #ident: &'__pin mut #ty,
+ #vis #name &'__pin mut #ty,
),
quote!(
- #ident: &mut #this.#ident,
+ #name &mut #this.#member,
),
)
}
@@ -355,24 +376,52 @@ fn generate_projections(
let structurally_pinned_fields_docs = fields
.iter()
.filter(|f| f.pinned)
- .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap()));
+ .map(|f| format!(" - {}", f.member.display_name()));
let not_structurally_pinned_fields_docs = fields
.iter()
.filter(|f| !f.pinned)
- .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap()));
+ .map(|f| format!(" - {}", f.member.display_name()));
let docs = format!(" Pin-projections of [`{ident}`]");
+ let (projection_def, projection_init) = if is_tuple_struct {
+ (
+ quote! {
+ #vis struct #projection #generics_with_pin_lt (
+ #(#fields_decl)*
+ ::core::marker::PhantomData<&'__pin mut ()>,
+ ) #whr;
+ },
+ quote! {
+ #projection(
+ #(#fields_proj)*
+ ::core::marker::PhantomData,
+ )
+ },
+ )
+ } else {
+ (
+ quote! {
+ #vis struct #projection #generics_with_pin_lt
+ #whr
+ {
+ #(#fields_decl)*
+ ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>,
+ }
+ },
+ quote! {
+ #projection {
+ #(#fields_proj)*
+ ___pin_phantom_data: ::core::marker::PhantomData,
+ }
+ },
+ )
+ };
quote! {
#[doc = #docs]
// Allow `non_snake_case` since the same warning will be emitted on
// the struct definition.
#[allow(dead_code, non_snake_case)]
#[doc(hidden)]
- #vis struct #projection #generics_with_pin_lt
- #whr
- {
- #(#fields_decl)*
- ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>,
- }
+ #projection_def
impl #impl_generics #ident #ty_generics
#whr
@@ -390,10 +439,7 @@ impl #impl_generics #ident #ty_generics
) -> #projection #ty_generics_with_pin_lt {
// SAFETY: we only give access to `&mut` for fields not structurally pinned.
let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
- #projection {
- #(#fields_proj)*
- ___pin_phantom_data: ::core::marker::PhantomData,
- }
+ #projection_init
}
}
}
@@ -414,11 +460,9 @@ fn generate_the_pin_data(
let field_accessors = fields
.iter()
.map(|f| {
- let Field { vis, ident, ty, .. } = f.field;
-
- let field_name = ident
- .as_ref()
- .expect("only structs with named fields are supported");
+ let Field { vis, ty, .. } = f.field;
+ let field_name = f.member.as_ident();
+ let member = &f.member;
let pin_marker = if f.pinned {
quote!(Pinned)
} else {
@@ -443,7 +487,7 @@ fn generate_the_pin_data(
// - If `#pin_marker` is `Pinned`, the corresponding field is structurally
// pinned.
// - Other safety requirements follows the safety requirement.
- unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#field_name) }
+ unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) }
}
}
})
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index ed18ab7d45e6..ed2c78f0658f 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
-use proc_macro2::TokenStream;
-use syn::Attribute;
+use proc_macro2::{Ident, TokenStream};
+use quote::format_ident;
+use syn::{Attribute, Index, Member};
pub(crate) trait AttrListExt {
fn extract_cfg_attrs(&mut self) -> Vec<TokenStream>;
@@ -25,3 +26,29 @@ fn extract_cfg_attrs(&mut self) -> Vec<TokenStream> {
cfg
}
}
+
+pub(crate) trait MemberExt {
+ /// Returns an identifier for the member.
+ ///
+ /// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead.
+ fn as_ident(&self) -> Ident;
+
+ /// Obtain a display name for the member in diagnostics.
+ fn display_name(&self) -> String;
+}
+
+impl MemberExt for Member {
+ fn as_ident(&self) -> Ident {
+ match self {
+ Member::Named(ident) => ident.clone(),
+ Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"),
+ }
+ }
+
+ fn display_name(&self) -> String {
+ match self {
+ Member::Named(ident) => format!("`{ident}`"),
+ Member::Unnamed(Index { index, .. }) => format!("index `{index}`"),
+ }
+ }
+}
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index 7600cdbbbf98..bf77b76c43c8 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -304,6 +304,9 @@
/// This macro enables the use of the [`pin_init!`] macro. When pin-initializing a `struct`,
/// then `#[pin]` directs the type of initializer that is required.
///
+/// Tuple structs are supported as well. Their fields have no names, so the generated projection
+/// is a tuple struct too and its fields are accessed by index.
+///
/// If your `struct` implements `Drop`, then you need to add `PinnedDrop` as arguments to this
/// macro, and change your `Drop` implementation to `PinnedDrop` annotated with
/// `#[`[`macro@pinned_drop`]`]`, since dropping pinned values requires extra care.
@@ -327,6 +330,26 @@
/// }
/// ```
///
+/// The same as a tuple struct, projected by index:
+///
+/// ```
+/// # #![feature(allocator_api)]
+/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*;
+/// use core::pin::Pin;
+/// use pin_init::pin_data;
+///
+/// enum Command {
+/// /* ... */
+/// }
+///
+/// #[pin_data]
+/// struct DriverData(#[pin] CMutex<Vec<Command>>, Box<[u8; 1024 * 1024]>);
+///
+/// fn queue(data: Pin<&mut DriverData>) -> Pin<&mut CMutex<Vec<Command>>> {
+/// data.project().0
+/// }
+/// ```
+///
/// ```
/// # #![feature(allocator_api)]
/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*;
--
2.54.0