* [PATCH 1/8] rust: pin-init: internal: init: forget guards in reverse order
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 2/8] rust: pin-init: internal: init: set span location of guards Gary Guo
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
Currently, the forget order is the same as initialization order, this is
the wrong way around compared to their drop order when going out-of-scope.
The wrong order will cause borrow checker errors if there are lifetime
dependencies between the guards. This does not matter for now because there
cannot be any lifetime dependency; it would matter for self-referential
borrows.
Reverse the order to be consistent with their out-of-scope drop order.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/init.rs | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index 6605e04d49df..f0b23d0311ac 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -361,8 +361,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi
/// Generate the code that initializes the fields of the struct using the initializers in `field`.
fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -> TokenStream {
- let mut guards = vec![];
- let mut guard_attrs = vec![];
+ let mut forget_guards = vec![];
let mut res = TokenStream::new();
for InitializerField { attrs, kind } in fields {
let cfgs = {
@@ -474,17 +473,19 @@ fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -
#binding
});
- guards.push(guard);
- guard_attrs.push(cfgs);
+ forget_guards.push(quote_spanned! { span =>
+ #(#cfgs)*
+ ::core::mem::forget(#guard);
+ });
}
+
+ forget_guards.reverse();
+
quote! {
#res
// If execution reaches this point, all fields have been initialized. Therefore we can now
// dismiss the guards by forgetting them.
- #(
- #(#guard_attrs)*
- ::core::mem::forget(#guards);
- )*
+ #(#forget_guards)*
}
}
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 2/8] rust: pin-init: internal: init: set span location of guards
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
2026-10-06 15:29 ` [PATCH 1/8] rust: pin-init: internal: init: forget guards in reverse order Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 3/8] rust: pin-init: internal: add utility to handle generics merging Gary Guo
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
Set the span location of the guards to point to the identifier. The
generated diagnostic is still not ideal, as ideally it points the place
where the identifier is used rather than the identifier name in
initializer, but is still an improvement to status quo.
Before the change:
error[E0505]: cannot move out of value because it is borrowed
--> tests/ui/compile-fail/init/accessor_lifetime.rs:8:13
|
8 | let _ = init!(Foo {
| _____________^
9 | | x: 0,
10 | | _: {
11 | | let _: &'static usize = x;
| | -------------- type annotation requires that borrow lasts for `'static`
12 | | },
13 | | });
| | ^
| | |
| |______move out of value occurs here
| borrow of value occurs here
After the change:
error[E0505]: cannot move out of value because it is borrowed
--> tests/ui/compile-fail/init/accessor_lifetime.rs:9:9
|
9 | x: 0,
| ^
| |
| move out of value occurs here
| borrow of value occurs here
10 | _: {
11 | let _: &'static usize = x;
| -------------- type annotation requires that borrow lasts for `'static`
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/init.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index f0b23d0311ac..80e4dc068d2e 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -413,7 +413,7 @@ fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -
};
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
- let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
+ let guard = format_ident!("__{ident}_guard", span = span);
let full_span = kind.span().resolved_at(Span::mixed_site());
let init = match kind {
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 3/8] rust: pin-init: internal: add utility to handle generics merging
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
2026-10-06 15:29 ` [PATCH 1/8] rust: pin-init: internal: init: forget guards in reverse order Gary Guo
2026-10-06 15:29 ` [PATCH 2/8] rust: pin-init: internal: init: set span location of guards Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 4/8] rust: pin-init: internal: add utility for extract a single attribute Gary Guo
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
There're many cases where we want to merge multiple generics together.
Currently this is done by cloning generics and modifying it, making the
code less readable.
Add a utility code that allow printing multiple generics together, ensuring
correct ordering between lifetime and other parameters.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 10 +-
rust/pin-init/internal/src/util.rs | 164 ++++++++++++++++++++++++++++++++-
2 files changed, 168 insertions(+), 6 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 982a38ceea93..4ffa35bbbccd 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -321,10 +321,12 @@ fn generate_projections(
is_tuple_struct: bool,
fields: &[FieldInfo<'_>],
) -> TokenStream {
- let (impl_generics, ty_generics, _) = generics.split_for_impl();
- let mut generics_with_pin_lt = generics.clone();
- generics_with_pin_lt.params.insert(0, parse_quote!('__pin));
- let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl();
+ let pin_lt_generics: Generics = parse_quote!(<'__pin>);
+ let generics_with_pin_lt = CombinedGenerics(vec![&pin_lt_generics, generics]);
+
+ let (impl_generics, ty_generics, whr) = generics.split_for_impl();
+ let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl();
+
let projection = format_ident!("{ident}Projection");
let this = format_ident!("this");
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index 0b5c28b21091..2a57a25b70ae 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use proc_macro2::{Ident, TokenStream};
-use quote::format_ident;
-use syn::{Attribute, Index, Member};
+use quote::{format_ident, ToTokens};
+use syn::{Attribute, GenericParam, Generics, Index, Member, Token};
pub(crate) trait AttrListExt {
fn extract_cfg_attrs(&mut self) -> Vec<TokenStream>;
@@ -52,3 +52,163 @@ fn display_name(&self) -> String {
}
}
}
+
+pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>);
+pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>);
+pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>);
+
+impl CombinedGenerics<'_> {
+ pub(crate) fn split_for_impl(
+ &self,
+ ) -> (
+ CombinedImplGenerics<'_>,
+ CombinedTypeGenerics<'_>,
+ // A stub type so `split_for_impl` signature matches that of `syn`'s.
+ impl Sized,
+ ) {
+ (CombinedImplGenerics(self), CombinedTypeGenerics(self), ())
+ }
+}
+
+impl ToTokens for CombinedGenerics<'_> {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ // Most of the time we are only adding lifetimes, so we prefer to place
+ // the user provided generics last.
+ self.0
+ .last()
+ .and_then(|x| x.lt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+
+ let comma: Token![,] = Default::default();
+
+ // Output lifetimes first.
+ for generics in self.0.iter() {
+ for param in generics.params.pairs() {
+ if let GenericParam::Lifetime(lt) = param.value() {
+ lt.to_tokens(tokens);
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+ }
+
+ for generics in self.0.iter() {
+ for param in generics.params.pairs() {
+ if let GenericParam::Lifetime(_) = param.value() {
+ continue;
+ };
+ param.value().to_tokens(tokens);
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+
+ self.0
+ .last()
+ .and_then(|x| x.gt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+ }
+}
+
+impl ToTokens for CombinedImplGenerics<'_> {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ self.0
+ .0
+ .last()
+ .and_then(|x| x.lt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+
+ let comma: Token![,] = Default::default();
+
+ // Output lifetimes first.
+ for generics in self.0 .0.iter() {
+ for param in generics.params.pairs() {
+ if let GenericParam::Lifetime(lt) = param.value() {
+ lt.to_tokens(tokens);
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+ }
+
+ for generics in self.0 .0.iter() {
+ for param in generics.params.pairs() {
+ // Leave out defaults.
+ match param.value() {
+ GenericParam::Lifetime(_) => continue,
+ GenericParam::Type(param) => {
+ param.ident.to_tokens(tokens);
+ if !param.bounds.is_empty() {
+ param
+ .colon_token
+ .unwrap_or_else(Default::default)
+ .to_tokens(tokens);
+ param.bounds.to_tokens(tokens);
+ }
+ }
+ GenericParam::Const(param) => {
+ param.const_token.to_tokens(tokens);
+ param.ident.to_tokens(tokens);
+ param.colon_token.to_tokens(tokens);
+ param.ty.to_tokens(tokens);
+ }
+ }
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+
+ self.0
+ .0
+ .last()
+ .and_then(|x| x.gt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+ }
+}
+
+impl ToTokens for CombinedTypeGenerics<'_> {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ self.0
+ .0
+ .last()
+ .and_then(|x| x.lt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+
+ let comma: Token![,] = Default::default();
+
+ // Output lifetimes first.
+ for generics in self.0 .0.iter() {
+ for param in generics.params.pairs() {
+ if let GenericParam::Lifetime(lt) = param.value() {
+ // Leave out bounds
+ lt.lifetime.to_tokens(tokens);
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+ }
+
+ for generics in self.0 .0.iter() {
+ for param in generics.params.pairs() {
+ // Leave out bounds and defaults.
+ match param.value() {
+ GenericParam::Lifetime(_) => continue,
+ GenericParam::Type(param) => {
+ param.ident.to_tokens(tokens);
+ }
+ GenericParam::Const(param) => {
+ param.ident.to_tokens(tokens);
+ }
+ }
+ param.punct().unwrap_or(&&comma).to_tokens(tokens);
+ }
+ }
+
+ self.0
+ .0
+ .last()
+ .and_then(|x| x.gt_token)
+ .unwrap_or_default()
+ .to_tokens(tokens);
+ }
+}
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 4/8] rust: pin-init: internal: add utility for extract a single attribute
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
` (2 preceding siblings ...)
2026-10-06 15:29 ` [PATCH 3/8] rust: pin-init: internal: add utility to handle generics merging Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 5/8] rust: pin-init: internal: pin_data: move projection struct into `const _: ()` block Gary Guo
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
More attributes are to be added which has the requirement that they should
only be specified once. Add a utility code to extract attributes for a
specific path and report error when appeared multiple times.
Convert `#[pin]` parsing to use the added utility.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 9 ++-------
rust/pin-init/internal/src/util.rs | 25 +++++++++++++++++++++++++
2 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 4ffa35bbbccd..ac83f0f73e61 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -137,12 +137,7 @@ pub(crate) fn pin_data(
.iter_mut()
.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();
- if pinned_count > 1 {
- dcx.error(&field, "#[pin] attribute specified more than once");
- }
+ let pinned = field.attrs.extract_single_attr(dcx, "pin").is_some();
assert!(
!field.attrs.iter().any(|a| a.path().is_ident("cfg")),
@@ -159,7 +154,7 @@ pub(crate) fn pin_data(
FieldInfo {
field: &*field,
member,
- pinned: pinned_count != 0,
+ pinned,
}
})
.collect();
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index 2a57a25b70ae..3ce498cea754 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -4,8 +4,15 @@
use quote::{format_ident, ToTokens};
use syn::{Attribute, GenericParam, Generics, Index, Member, Token};
+use crate::DiagCtxt;
+
pub(crate) trait AttrListExt {
fn extract_cfg_attrs(&mut self) -> Vec<TokenStream>;
+
+ /// Extract attribute with identifier `path`.
+ ///
+ /// Report error if the attribute appears multiple times.
+ fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option<Attribute>;
}
impl AttrListExt for Vec<Attribute> {
@@ -25,6 +32,24 @@ fn extract_cfg_attrs(&mut self) -> Vec<TokenStream> {
cfg
}
+
+ fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option<Attribute> {
+ // FIXME: Replace with `extract_if` when MSRV >= 1.85.
+ let attr_pos = self.iter().position(|attr| attr.path().is_ident(path))?;
+ let attr = self.remove(attr_pos);
+ self.retain(|attr| {
+ if !attr.path().is_ident(path) {
+ return true;
+ }
+
+ dcx.error(
+ attr,
+ format!("`#[{path}]` attribute specified more than once"),
+ );
+ false
+ });
+ Some(attr)
+ }
}
pub(crate) trait MemberExt {
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 5/8] rust: pin-init: internal: pin_data: move projection struct into `const _: ()` block
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
` (3 preceding siblings ...)
2026-10-06 15:29 ` [PATCH 4/8] rust: pin-init: internal: add utility for extract a single attribute Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 6/8] rust: pin-init: internal: pin_data: extract `#[cfg]` processing to its own function Gary Guo
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
Most users would not need to mention the generated projection struct. With
the upcoming self-referential support, there needs to be additional
projection structs. Instead of give them publicly visible names, simply
make the name not publicly accessible.
A heavy user of pin projection outside kernel is for async programming.
I've checked tokio codebase there is a single use case and that's for
enums.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index ac83f0f73e61..3acced1fe806 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -186,10 +186,10 @@ pub(crate) fn pin_data(
Ok(quote! {
#struct_
- #projections
// We put the rest into this const item, because it then will not be accessible to anything
// outside.
const _: () = {
+ #projections
#the_pin_data
#unpin_impl
#drop_impl
@@ -322,7 +322,6 @@ fn generate_projections(
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl();
- let projection = format_ident!("{ident}Projection");
let this = format_ident!("this");
let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields
@@ -371,13 +370,13 @@ fn generate_projections(
let (projection_def, projection_init) = if is_tuple_struct {
(
quote! {
- #vis struct #projection #generics_with_pin_lt (
+ #vis struct __Projection #generics_with_pin_lt (
#(#fields_decl)*
::core::marker::PhantomData<&'__pin mut ()>,
) #whr;
},
quote! {
- #projection(
+ __Projection(
#(#fields_proj)*
::core::marker::PhantomData,
)
@@ -386,7 +385,7 @@ fn generate_projections(
} else {
(
quote! {
- #vis struct #projection #generics_with_pin_lt
+ #vis struct __Projection #generics_with_pin_lt
#whr
{
#(#fields_decl)*
@@ -394,7 +393,7 @@ fn generate_projections(
}
},
quote! {
- #projection {
+ __Projection {
#(#fields_proj)*
___pin_phantom_data: ::core::marker::PhantomData,
}
@@ -422,7 +421,7 @@ impl #impl_generics #ident #ty_generics
#[inline]
#vis fn project<'__pin>(
self: ::core::pin::Pin<&'__pin mut Self>,
- ) -> #projection #ty_generics_with_pin_lt {
+ ) -> __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_init
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 6/8] rust: pin-init: internal: pin_data: extract `#[cfg]` processing to its own function
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
` (4 preceding siblings ...)
2026-10-06 15:29 ` [PATCH 5/8] rust: pin-init: internal: pin_data: move projection struct into `const _: ()` block Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 7/8] rust: pin-init: internal: pin_data: rename `'__pin` lifetime to `'__this` Gary Guo
2026-10-06 15:29 ` [PATCH 8/8] rust: pin-init: internal: pin_data: parse into `StructInfo` before generating Gary Guo
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
The `pin_data` function is getting big. Split the `#[cfg]` handling which
does not interact with other part of expansion to its own `expand_with_cfg`
function.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/lib.rs | 3 ++-
rust/pin-init/internal/src/pin_data.rs | 13 +++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs
index 07b3d33d2282..01e19fcd31fb 100644
--- a/rust/pin-init/internal/src/lib.rs
+++ b/rust/pin-init/internal/src/lib.rs
@@ -22,7 +22,8 @@
#[proc_macro_attribute]
pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
- DiagCtxt::for_item(|dcx| pin_data::pin_data(syn::parse(args)?, syn::parse(input)?, dcx)).into()
+ DiagCtxt::for_item(|dcx| pin_data::expand_with_cfg(syn::parse(args)?, syn::parse(input)?, dcx))
+ .into()
}
#[proc_macro_attribute]
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 3acced1fe806..a4d6965c1572 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -8,7 +8,8 @@
punctuated::Punctuated,
spanned::Spanned,
visit_mut::VisitMut,
- Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility,
+ Field, Fields, Generics, Ident, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath,
+ Visibility,
};
use crate::{
@@ -54,7 +55,7 @@ struct FieldInfo<'a> {
pinned: bool,
}
-pub(crate) fn pin_data(
+pub(crate) fn expand_with_cfg(
args: Args,
input: Item,
dcx: &mut DiagCtxt,
@@ -120,6 +121,14 @@ pub(crate) fn pin_data(
));
}
+ expand(args, struct_, dcx)
+}
+
+fn expand(
+ args: Args,
+ mut struct_: ItemStruct,
+ dcx: &mut DiagCtxt,
+) -> Result<TokenStream, ErrorGuaranteed> {
// 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.
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 7/8] rust: pin-init: internal: pin_data: rename `'__pin` lifetime to `'__this`
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
` (5 preceding siblings ...)
2026-10-06 15:29 ` [PATCH 6/8] rust: pin-init: internal: pin_data: extract `#[cfg]` processing to its own function Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
2026-10-06 15:29 ` [PATCH 8/8] rust: pin-init: internal: pin_data: parse into `StructInfo` before generating Gary Guo
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
"pin" is a very overloaded word in pin-init. For the generated projection
structs, the lifetime refer to that of the whole struct, so rename the
lifetime used in expansion from `'__pin` to `'__this` instead.
The `__pin_phantom_data` field is renamed to `__this` for the same reason
and for clarity purpose, capture a `PhantomData` of the mutable reference
to the struct.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index a4d6965c1572..6e18bc9e890a 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -325,11 +325,11 @@ fn generate_projections(
is_tuple_struct: bool,
fields: &[FieldInfo<'_>],
) -> TokenStream {
- let pin_lt_generics: Generics = parse_quote!(<'__pin>);
- let generics_with_pin_lt = CombinedGenerics(vec![&pin_lt_generics, generics]);
+ let this_lt_generics: Generics = parse_quote!(<'__this>);
+ let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
- let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl();
+ let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl();
let this = format_ident!("this");
@@ -348,7 +348,7 @@ fn generate_projections(
if field.pinned {
(
quote!(
- #vis #name ::core::pin::Pin<&'__pin mut #ty>,
+ #vis #name ::core::pin::Pin<&'__this mut #ty>,
),
quote!(
// SAFETY: this field is structurally pinned.
@@ -358,7 +358,7 @@ fn generate_projections(
} else {
(
quote!(
- #vis #name &'__pin mut #ty,
+ #vis #name &'__this mut #ty,
),
quote!(
#name &mut #this.#member,
@@ -379,9 +379,9 @@ fn generate_projections(
let (projection_def, projection_init) = if is_tuple_struct {
(
quote! {
- #vis struct __Projection #generics_with_pin_lt (
+ #vis struct __Projection #generics_with_this_lt (
#(#fields_decl)*
- ::core::marker::PhantomData<&'__pin mut ()>,
+ ::core::marker::PhantomData<&'__this mut #ident #ty_generics>,
) #whr;
},
quote! {
@@ -394,17 +394,17 @@ fn generate_projections(
} else {
(
quote! {
- #vis struct __Projection #generics_with_pin_lt
+ #vis struct __Projection #generics_with_this_lt
#whr
{
#(#fields_decl)*
- ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>,
+ __this: ::core::marker::PhantomData<&'__this mut #ident #ty_generics>,
}
},
quote! {
__Projection {
#(#fields_proj)*
- ___pin_phantom_data: ::core::marker::PhantomData,
+ __this: ::core::marker::PhantomData,
}
},
)
@@ -428,9 +428,9 @@ impl #impl_generics #ident #ty_generics
/// These fields are **not** structurally pinned:
#(#[doc = #not_structurally_pinned_fields_docs])*
#[inline]
- #vis fn project<'__pin>(
- self: ::core::pin::Pin<&'__pin mut Self>,
- ) -> __Projection #ty_generics_with_pin_lt {
+ #vis fn project<'__this>(
+ self: ::core::pin::Pin<&'__this mut Self>,
+ ) -> __Projection #ty_generics_with_this_lt {
// SAFETY: we only give access to `&mut` for fields not structurally pinned.
let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
#projection_init
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 8/8] rust: pin-init: internal: pin_data: parse into `StructInfo` before generating
2026-10-06 15:29 [PATCH 0/8] rust: pin-init: various cleanups to prepare for self-reference Gary Guo
` (6 preceding siblings ...)
2026-10-06 15:29 ` [PATCH 7/8] rust: pin-init: internal: pin_data: rename `'__pin` lifetime to `'__this` Gary Guo
@ 2026-10-06 15:29 ` Gary Guo
7 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-10-06 15:29 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
Organize parsing and generation into two separate phases, with a single
`StructInfo` in the middle. This allows analysis and processing to happen
with a single struct and avoid having to pass different information into
the token stream generation functions.
A drawback of this approach is that as we consume the `Fields`, we cannot
expand the original struct using `quote!(#struct_)` anymore, and the field
needs to be re-constructed in `generate_struct_def` function. However, this
is only a temporary drawback, as the upcoming pin-init self-referential
feature will require modification of the generated struct anyway.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/pin_data.rs | 162 +++++++++++++++++++++++----------
1 file changed, 112 insertions(+), 50 deletions(-)
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 6e18bc9e890a..03e893cf5475 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -8,8 +8,7 @@
punctuated::Punctuated,
spanned::Spanned,
visit_mut::VisitMut,
- Field, Fields, Generics, Ident, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath,
- Visibility,
+ Field, Fields, Generics, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath,
};
use crate::{
@@ -49,12 +48,19 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
}
}
-struct FieldInfo<'a> {
- field: &'a Field,
+struct FieldInfo {
+ field: Field,
member: Member,
pinned: bool,
}
+struct StructInfo {
+ args: Args,
+ struct_: ItemStruct,
+ fields: Vec<FieldInfo>,
+ is_tuple_struct: bool,
+}
+
pub(crate) fn expand_with_cfg(
args: Args,
input: Item,
@@ -141,11 +147,11 @@ fn expand(
replacer.visit_fields_mut(&mut struct_.fields);
let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_));
- let fields: Vec<FieldInfo<'_>> = struct_
+ let fields: Vec<FieldInfo> = struct_
.fields
- .iter_mut()
+ .into_iter()
.enumerate()
- .map(|(index, field)| {
+ .map(|(index, mut field)| {
let pinned = field.attrs.extract_single_attr(dcx, "pin").is_some();
assert!(
@@ -161,17 +167,25 @@ fn expand(
};
FieldInfo {
- field: &*field,
+ field,
member,
pinned,
}
})
.collect();
- for field in &fields {
+ struct_.fields = Fields::Unit;
+ let info = StructInfo {
+ args,
+ struct_,
+ fields,
+ is_tuple_struct,
+ };
+
+ for field in &info.fields {
if !field.pinned && is_phantom_pinned(&field.field.ty) {
dcx.warn(
- field.field,
+ &field.field,
format!(
"The field {} of type `PhantomPinned` only has an effect \
if it has the `#[pin]` attribute",
@@ -181,20 +195,14 @@ fn expand(
}
}
- 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,
- is_tuple_struct,
- &fields,
- );
- let the_pin_data =
- generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields);
+ let struct_def = generate_struct_def(&info);
+ let unpin_impl = generate_unpin_impl(&info);
+ let drop_impl = generate_drop_impl(&info);
+ let projections = generate_projections(&info);
+ let the_pin_data = generate_the_pin_data(&info);
Ok(quote! {
- #struct_
+ #struct_def
// We put the rest into this const item, because it then will not be accessible to anything
// outside.
const _: () = {
@@ -230,17 +238,63 @@ fn is_phantom_pinned(ty: &Type) -> bool {
}
}
-fn generate_unpin_impl(
- ident: &Ident,
- generics: &Generics,
- fields: &[FieldInfo<'_>],
-) -> TokenStream {
+fn generate_struct_def(info: &StructInfo) -> TokenStream {
+ let ItemStruct {
+ attrs,
+ vis,
+ struct_token,
+ ident,
+ generics,
+ fields: _,
+ semi_token,
+ } = &info.struct_;
+
+ let generated_fields = info.fields.iter().map(|field| {
+ let Field {
+ attrs,
+ vis,
+ mutability: _,
+ ident,
+ colon_token,
+ ty,
+ } = &field.field;
+
+ quote! {
+ #(#attrs)* #vis #ident #colon_token #ty
+ }
+ });
+
+ let whr = &generics.where_clause;
+
+ if info.is_tuple_struct {
+ quote!(
+ #(#attrs)*
+ #vis
+ #struct_token #ident #generics (#(#generated_fields,)*) #whr
+ #semi_token
+ )
+ } else {
+ quote!(
+ #(#attrs)*
+ #vis
+ #struct_token #ident #generics #whr {
+ #(#generated_fields,)*
+ }
+ #semi_token
+ )
+ }
+}
+
+fn generate_unpin_impl(info: &StructInfo) -> TokenStream {
+ let ItemStruct {
+ generics, ident, ..
+ } = &info.struct_;
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
let predicates = whr
.map(|x| &x.predicates)
.unwrap_or(const { &Punctuated::new() });
- let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| {
+ let pinned_fields = info.fields.iter().filter(|f| f.pinned).map(|f| {
let ident = f.member.as_ident();
let ty = &f.field.ty;
quote!(
@@ -271,9 +325,12 @@ impl #impl_generics ::core::marker::Unpin for #ident #ty_generics
}
}
-fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenStream {
+fn generate_drop_impl(info: &StructInfo) -> TokenStream {
+ let ItemStruct {
+ generics, ident, ..
+ } = &info.struct_;
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
- let has_pinned_drop = matches!(args, Args::PinnedDrop(_));
+ let has_pinned_drop = matches!(info.args, Args::PinnedDrop(_));
// We need to disallow normal `Drop` implementation, the exact behavior depends on whether
// `PinnedDrop` was specified in `args`.
if has_pinned_drop {
@@ -318,13 +375,13 @@ impl #impl_generics
}
}
-fn generate_projections(
- vis: &Visibility,
- ident: &Ident,
- generics: &Generics,
- is_tuple_struct: bool,
- fields: &[FieldInfo<'_>],
-) -> TokenStream {
+fn generate_projections(info: &StructInfo) -> TokenStream {
+ let ItemStruct {
+ vis,
+ ident,
+ generics,
+ ..
+ } = &info.struct_;
let this_lt_generics: Generics = parse_quote!(<'__this>);
let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
@@ -333,14 +390,15 @@ fn generate_projections(
let this = format_ident!("this");
- let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields
+ let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info
+ .fields
.iter()
.map(|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 name = (!info.is_tuple_struct).then(|| {
let ident = field.member.as_ident();
quote!(#ident:)
});
@@ -367,16 +425,18 @@ fn generate_projections(
}
})
.collect();
- let structurally_pinned_fields_docs = fields
+ let structurally_pinned_fields_docs = info
+ .fields
.iter()
.filter(|f| f.pinned)
.map(|f| format!(" - {}", f.member.display_name()));
- let not_structurally_pinned_fields_docs = fields
+ let not_structurally_pinned_fields_docs = info
+ .fields
.iter()
.filter(|f| !f.pinned)
.map(|f| format!(" - {}", f.member.display_name()));
let docs = format!(" Pin-projections of [`{ident}`]");
- let (projection_def, projection_init) = if is_tuple_struct {
+ let (projection_def, projection_init) = if info.is_tuple_struct {
(
quote! {
#vis struct __Projection #generics_with_this_lt (
@@ -439,22 +499,24 @@ impl #impl_generics #ident #ty_generics
}
}
-fn generate_the_pin_data(
- vis: &Visibility,
- struct_name: &Ident,
- generics: &Generics,
- fields: &[FieldInfo<'_>],
-) -> TokenStream {
+fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
+ let ItemStruct {
+ vis,
+ ident: struct_name,
+ generics,
+ ..
+ } = &info.struct_;
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
// For every field, we create an initializing projection function according to its projection
// type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be
// initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with
// `Unpinned` which allows initialization via `Init`.
- let field_accessors = fields
+ let field_accessors = info
+ .fields
.iter()
.map(|f| {
- let Field { vis, ty, .. } = f.field;
+ let Field { vis, ty, .. } = &f.field;
let field_name = f.member.as_ident();
let member = &f.member;
let pin_marker = if f.pinned {
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread