All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gary Guo <gary@garyguo.net>
To: "Benno Lossin" <lossin@kernel.org>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 Gary Guo <gary@garyguo.net>, Mohamad Alsadhan <mo@sdhn.cc>
Subject: [PATCH 2/4] rust: pin-init: internal: pin_data: support tuple struct projections
Date: Fri, 04 Sep 2026 15:12:30 +0100	[thread overview]
Message-ID: <20260904-tuple-struct-v1-2-72c50bd037fd@garyguo.net> (raw)
In-Reply-To: <20260904-tuple-struct-v1-0-72c50bd037fd@garyguo.net>

From: Mohamad Alsadhan <mo@sdhn.cc>

`#[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@sdhn.cc>
[ Moved utility code to util.rs as extension trait - Gary ]
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 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


  parent reply	other threads:[~2026-09-04 14:12 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 14:12 [PATCH 0/4] rust: pin-init: support tuple structs Gary Guo
2026-09-04 14:12 ` [PATCH 1/4] rust: pin-init: internal: extract utility code to new module Gary Guo
2026-09-04 14:12 ` Gary Guo [this message]
2026-09-04 14:12 ` [PATCH 3/4] rust: pin-init: internal: init: support tuple structs in `[pin_]init!` Gary Guo
2026-09-04 14:12 ` [PATCH 4/4] rust: pin-init: internal: init: support tuple struct constructor syntax Gary Guo
2026-09-14 11:33 ` [PATCH 0/4] rust: pin-init: support tuple structs Gary Guo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260904-tuple-struct-v1-2-72c50bd037fd@garyguo.net \
    --to=gary@garyguo.net \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=mo@sdhn.cc \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=work@onurozkan.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.