mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/20] rust: pin-init: create self references safely
@ 2026-10-08 19:24 Gary Guo
  2026-10-08 19:24 ` [PATCH v2 01/20] kbuild: rust: allow `clippy::comparison_chain` globally Gary Guo
                   ` (19 more replies)
  0 siblings, 20 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

This is a big series that add support for one field to reference a sibling
field in a safe and ergnonomic way. Unlike many other crates in the
userspace Rust ecosystem, no additional allocation is required, thus it
requires the struct to be pinned, which is exactly what pin-init provides.

This is a very powerful feature, and thus can raise concerns about whether
it is sound. I spent a lot of time studying the rules to make this sound,
and presented durirng Kangrejos [1].

The simple use case looks like this:

    #[pin_data]
    struct MyDriver<'bound> {
        dev: &'bound Device,
        #[pin]
        irq: irq::Registration<'bound, MyIrqHandler<'bound, 'bar>>,
        bar: Bar<'bound, BAR_SIZE>,
    }

You simply need to mention a lifetime that shares the name with the field.
There are more advanced use cases which require annotations like

    #[uses('bar: invariant)]

to explicitly declare that the lifetime is used in invariant manner, and
also

    where
        exists<'dev>: 'a

to create new existential lifetimes as a way to erase invariant lifetimes
that would otherwise bubble up to users. More info can be seen in my
Plumbers slide [2]. Note that the syntax for explicit variance annotation
has changed following discussions during Plumbers.

The initial plan was to upstream the simple use case only and iron out the
advanced features subsequently; however it turns out that DRM jobqueue
would need the variance annotation feature, and Nova `Cmdq` would need to
use the existential lifetime feature.

During Plumbers the upstream schedule is discussed, and instead it was
agreed that all the features should be upstreamed at once, but the usage of
the advanced feature usage limited to the pre-agreed users only to limit
the blast radius in case the feature needs to be reworked.

Detailed documentation about the pin-init self-reference feature would be
added the following cycle, when the usage of them become more clear.

The pull request on GitHub [3] has a bunch of test suites, which are not
synchronized to kernel tree.

Link: https://kangrejos.com/2026/Self%20referential%20pin-init.pdf [1]
Link: https://lpc.events/event/20/contributions/2498/attachments/2200/4855/presentation.pdf [2]
Link: https://github.com/Rust-for-Linux/pin-init/pull/181 [3]
Signed-off-by: Gary Guo <gary@garyguo.net>
---
Changes in v2:
- Add where bounds to covariance check (Sashiko)
- Use `NotVisible` mechanism for `with_project_ref` instead of filtering (Sashiko)
- Drop `pub` from `SelfRefSlot` (Sashiko)
- Fixed some stale comments (Sashiko)
- Picked up Benno's Ack.
- Link to v1: https://patch.msgid.link/20261008-dev-selfref-v1-0-6c1eb269fe57@garyguo.net

---
Gary Guo (20):
      kbuild: rust: allow `clippy::comparison_chain` globally
      rust: pin-init: internal: pin_data: infer self-referential struct
      rust: pin-init: internal: pin_data: rewrite fields that borrow others
      rust: pin-init: internal: pin_data: pin borrowed fields with wrapper
      rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle
      rust: pin-init: internal: pin_data: self-referential drop order checks
      rust: pin-init: internal: pin_data: check covariance of self-referential fields
      rust: pin-init: internal: pin_data: implement initialization of borrowed structs
      rust: pin-init: internal: pin_data: project self-referential fields
      rust: pin-init: internal: pin_data: add `with_project` method
      rust: pin-init: internal: pin_data: enable self-referential support
      rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order
      rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation
      rust: pin-init: internal: pin_data: support mutable borrows
      rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation
      rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance
      rust: pin-init: internal: pin_data: complete invariant borrow support
      rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible
      rust: pin-init: internal: pin_data: support shared projection
      rust: pin-init: internal: pin_data: support existential lifetimes

 Makefile                               |    2 +
 rust/pin-init/examples/selfref.rs      |   60 ++
 rust/pin-init/internal/src/init.rs     |   42 +-
 rust/pin-init/internal/src/pin_data.rs | 1563 +++++++++++++++++++++++++++++++-
 rust/pin-init/internal/src/util.rs     |  269 +++++-
 rust/pin-init/src/__internal.rs        |  357 +++++++-
 rust/pin-init/src/lib.rs               |    1 +
 7 files changed, 2246 insertions(+), 48 deletions(-)
---
base-commit: 0d9aa4b994379c6e0099737221ed421deb7e1a31
change-id: 20261006-dev-selfref-27c37abfa849

Best regards,
--  
Gary Guo <gary@garyguo.net>


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 01/20] kbuild: rust: allow `clippy::comparison_chain` globally
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 02/20] rust: pin-init: internal: pin_data: infer self-referential struct Gary Guo
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

The lint would suggest comparison chains to be converted to match on
`Ordering` instead. The suggestions do not always produce more readable
code, and also it can produce false-positive when not all ordering has a
branch.

The issue is known upstream and thus it has been downgraded from style
lints to pedantic lints, which makes it allow-by-default since Rust 1.87.0.
The current kernel MSRV is 1.85.0, and thus the lint still triggers; allow
it globally to match the updated upstream behaviour.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index 0f1b80100b47..2839c6c5045b 100644
--- a/Makefile
+++ b/Makefile
@@ -476,6 +476,7 @@ KBUILD_USERLDFLAGS := $(USERLDFLAGS)
 # These flags apply to all Rust code in the tree, including the kernel and
 # host programs.
 #
+# `-Aclippy::comparison_chain`: the lint has been demoted since 1.87.
 # `-Aclippy::unwrap_or_default`: the lint is buggy [1] and ignores our
 # MSRV. It can trigger depending on the optimization level.
 # [1] https://github.com/rust-lang/rust-clippy/issues/17379
@@ -494,6 +495,7 @@ export rust_common_flags := --edition=2021 \
 			    -Wclippy::cast_lossless \
 			    -Aclippy::collapsible_if \
 			    -Aclippy::collapsible_match \
+			    -Aclippy::comparison_chain \
 			    -Wclippy::ignored_unit_patterns \
 			    -Aclippy::incompatible_msrv \
 			    -Wclippy::mut_mut \

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 02/20] rust: pin-init: internal: pin_data: infer self-referential struct
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
  2026-10-08 19:24 ` [PATCH v2 01/20] kbuild: rust: allow `clippy::comparison_chain` globally Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 03/20] rust: pin-init: internal: pin_data: rewrite fields that borrow others Gary Guo
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

As a first step towards adding self-referential data structures in
pin-init, add parsing support.

Scan all field types for unbounded lifetimes, and if the names that of
fields, it is inferred as a self-referential field lifetime. No explicit
annotations are supported yet.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 162 ++++++++++++++++++++++++++++++++-
 rust/pin-init/internal/src/util.rs     |  94 ++++++++++++++++++-
 2 files changed, 253 insertions(+), 3 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 03e893cf5475..cf6142cd656d 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
+use std::collections::{BTreeMap, BTreeSet};
+
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote, ToTokens};
 use syn::{
@@ -7,8 +9,10 @@
     parse_quote, parse_quote_spanned,
     punctuated::Punctuated,
     spanned::Spanned,
+    visit::Visit,
     visit_mut::VisitMut,
-    Field, Fields, Generics, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath,
+    Field, Fields, Generics, Ident, Index, Item, ItemStruct, Lifetime, Member, PathSegment, Type,
+    TypePath,
 };
 
 use crate::{
@@ -48,10 +52,69 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
     }
 }
 
+/// Description of how a field is borrowed.
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
+enum BorrowedKind {
+    /// Implicitly inferreed.
+    #[default]
+    Shared,
+}
+
+/// Information about a borrowed field.
+#[expect(unused)]
+struct BorrowedInfo {
+    kind: BorrowedKind,
+    /// Field lifetime for this field.
+    lifetime: Lifetime,
+}
+
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
+enum Variance {
+    /// Implicitly inferred variance.
+    #[default]
+    Covariant,
+}
+
+/// Information about field lifetimes captured in a type.
+#[expect(unused)]
+struct Capture {
+    variance: Variance,
+    /// Lifetime to be captured.
+    lifetime: Lifetime,
+}
+
+impl std::borrow::Borrow<Lifetime> for Capture {
+    fn borrow(&self) -> &Lifetime {
+        &self.lifetime
+    }
+}
+
+impl PartialEq for Capture {
+    fn eq(&self, other: &Self) -> bool {
+        self.lifetime == other.lifetime
+    }
+}
+
+impl Eq for Capture {}
+
+impl PartialOrd for Capture {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Capture {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.lifetime.cmp(&other.lifetime)
+    }
+}
+
 struct FieldInfo {
     field: Field,
     member: Member,
     pinned: bool,
+    borrowed: Option<BorrowedInfo>,
+    captures: BTreeSet<Capture>,
 }
 
 struct StructInfo {
@@ -59,6 +122,7 @@ struct StructInfo {
     struct_: ItemStruct,
     fields: Vec<FieldInfo>,
     is_tuple_struct: bool,
+    self_referential: bool,
 }
 
 pub(crate) fn expand_with_cfg(
@@ -147,7 +211,22 @@ fn expand(
     replacer.visit_fields_mut(&mut struct_.fields);
 
     let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_));
-    let fields: Vec<FieldInfo> = struct_
+
+    // Collect all bound lifetimes from generics.
+    let bound_lifetimes: BTreeSet<&Lifetime> =
+        struct_.generics.lifetimes().map(|x| &x.lifetime).collect();
+    // Collect all fields.
+    let field_idx_map: BTreeMap<Ident, usize> = struct_
+        .fields
+        .iter()
+        .enumerate()
+        .filter_map(|(index, field)| Some((field.ident.clone()?, index)))
+        .collect();
+
+    // Keep track on fields being implicitly borrowed by being mentioned.
+    let mut implicitly_borrowed = BTreeSet::new();
+
+    let mut fields: Vec<FieldInfo> = struct_
         .fields
         .into_iter()
         .enumerate()
@@ -166,16 +245,88 @@ fn expand(
                 }),
             };
 
+            let mut captures = BTreeSet::new();
+            let wildcard_variance = Variance::default();
+
+            // Infer lifetime based on the field referenced.
+            // Bound lifetimes from struct generics take priority.
+            //
+            // For example,
+            // ```
+            // struct Foo<'a> {
+            //     bar: &'a (),
+            //     a: u32,
+            // }
+            // ```
+            // would not be inferred as self-referential because `'a` is already bound by the
+            // struct generics.
+            Lifetime::visitor(|lt| {
+                if bound_lifetimes.contains(lt) || captures.contains(lt) {
+                    return;
+                }
+
+                if !field_idx_map.contains_key(&lt.ident) {
+                    dcx.error(
+                        lt,
+                        format!("`{lt}` is neither a lifetime in generics nor a field name"),
+                    );
+                    return;
+                }
+
+                captures.insert(Capture {
+                    variance: wildcard_variance,
+                    lifetime: lt.clone(),
+                });
+            })
+            .visit_type(&field.ty);
+
+            for capture in captures.iter() {
+                implicitly_borrowed.insert(capture.lifetime.ident.clone());
+            }
+
             FieldInfo {
                 field,
                 member,
                 pinned,
+                borrowed: None,
+                captures,
             }
         })
         .collect();
 
+    for field_name in implicitly_borrowed.into_iter() {
+        let field = &mut fields[field_idx_map[&field_name]];
+
+        // If field is not explicit marked as borrowed, infer a shared borrow.
+        if field.borrowed.is_none() {
+            field.borrowed = Some(BorrowedInfo {
+                kind: BorrowedKind::Shared,
+                // Obtaining from `field` instead of `field_name` for the correct span.
+                lifetime: Lifetime::from_ident(&field.member.as_ident()),
+            });
+        }
+    }
+
+    // Check that field lifetimes do not appear in the bounds.
+    Lifetime::visitor(|lt| {
+        if bound_lifetimes.contains(&lt) {
+            return;
+        }
+
+        if field_idx_map.contains_key(&lt.ident) {
+            // Forbid the use of field lifetimes within bounds.
+            dcx.error(lt, "field lifetimes cannot be used in bounds");
+        }
+
+        // Otherwise this is completely unbound. Let Rust compiler produce that error instead.
+    })
+    .visit_generics(&struct_.generics);
+
     struct_.fields = Fields::Unit;
     let info = StructInfo {
+        self_referential: fields
+            .iter()
+            .any(|f| !f.captures.is_empty() || f.borrowed.is_some()),
         args,
         struct_,
         fields,
@@ -195,6 +346,13 @@ fn expand(
         }
     }
 
+    if info.self_referential {
+        dcx.error(
+            &info.struct_.ident,
+            "self-referential support is not fully implemented",
+        );
+    }
+
     let struct_def = generate_struct_def(&info);
     let unpin_impl = generate_unpin_impl(&info);
     let drop_impl = generate_drop_impl(&info);
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index 3ce498cea754..67ebb333710f 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -1,8 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
+use std::collections::BTreeSet;
+
 use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, ToTokens};
-use syn::{Attribute, GenericParam, Generics, Index, Member, Token};
+use syn::{
+    visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, Token,
+};
 
 use crate::DiagCtxt;
 
@@ -237,3 +241,91 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
             .to_tokens(tokens);
     }
 }
+
+pub(crate) trait LifetimeExt {
+    /// Get a visitor that call the provided function for all unbound lifetimes.
+    fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>;
+
+    /// Obtain a lifetime from a identifier.
+    ///
+    /// The created lifetime has the same span.
+    fn from_ident(ident: &Ident) -> Self;
+}
+
+impl LifetimeExt for Lifetime {
+    fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a> {
+        LifetimeVisitor {
+            bound: BTreeSet::new(),
+            visit: f,
+        }
+    }
+
+    fn from_ident(ident: &Ident) -> Self {
+        Lifetime {
+            apostrophe: ident.span(),
+            ident: ident.clone(),
+        }
+    }
+}
+
+struct LifetimeVisitor<'a, F> {
+    bound: BTreeSet<&'a Lifetime>,
+    visit: F,
+}
+
+impl<'a, F> LifetimeVisitor<'a, F> {
+    fn with_bound_lifetimes(
+        &mut self,
+        bound: Option<&'a BoundLifetimes>,
+        f: impl FnOnce(&mut Self),
+    ) {
+        // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`,
+        // the lifetimes in the binder are bound and should not be visited.
+
+        let mut to_remove = Vec::new();
+        if let Some(bound) = bound {
+            for lt in &bound.lifetimes {
+                let GenericParam::Lifetime(lt) = lt else {
+                    continue;
+                };
+                if !self.bound.contains(&&lt.lifetime) {
+                    self.bound.insert(&lt.lifetime);
+                    to_remove.push(&lt.lifetime);
+                }
+            }
+        }
+
+        f(self);
+
+        for lt in to_remove {
+            self.bound.remove(lt);
+        }
+    }
+}
+
+impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> {
+    fn visit_lifetime(&mut self, lt: &'a Lifetime) {
+        if lt.ident == "static" {
+            return;
+        }
+
+        if !self.bound.contains(lt) {
+            (self.visit)(lt);
+        }
+    }
+
+    fn visit_trait_bound(&mut self, bound: &'a syn::TraitBound) {
+        self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| {
+            this.visit_path(&bound.path)
+        });
+    }
+
+    fn visit_type_bare_fn(&mut self, bare_fn: &'a syn::TypeBareFn) {
+        self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| {
+            for input in bare_fn.inputs.iter() {
+                this.visit_bare_fn_arg(input);
+            }
+            this.visit_return_type(&bare_fn.output);
+        });
+    }
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 03/20] rust: pin-init: internal: pin_data: rewrite fields that borrow others
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
  2026-10-08 19:24 ` [PATCH v2 01/20] kbuild: rust: allow `clippy::comparison_chain` globally Gary Guo
  2026-10-08 19:24 ` [PATCH v2 02/20] rust: pin-init: internal: pin_data: infer self-referential struct Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 04/20] rust: pin-init: internal: pin_data: pin borrowed fields with wrapper Gary Guo
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Fields that borrow other fields have lifetimes that are within the struct
and these are not part of the struct generics. Therefore, these fields need
to have their lifetime erased.

A naive implementation would be to replace their lifetimes with `'static`.
However, doing so is unsound for multiple reasons:
* Users may directly access such field with field access syntax, and get
  exposed with wrong lifetime;
* Auto trait implementations will cause the struct to be implementing auto
  traits when the type only implements the auto trait for specific
  lifetime. This is similar to how specialization can be unsound if
  specialized on lifetime.

Create a `Erase` type, which has `for<'a> fn(&'a ()) -> Foo<'a>` as generic
parameter. Internally, it uses a helper trait to resolve that to
`Foo<'static>`. The first issue is solved by not exposing any public
accessor on that type. The second issue is solved by add custom `Send` and
`Sync` implementations that requires `Send` to be implemented for all
lifetimes, thus closing the lifetime specialization hole. The actual
implementation is a bit more convoluted because it supports erasing
multiple lifetimes.

This is more or less a stable polyfill of the unstable `unsafe_binder`
feature, without `unsafe_binders`'s no drop glue requirement.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 16 +++++++
 rust/pin-init/src/__internal.rs        | 77 ++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index cf6142cd656d..36678843251c 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -417,6 +417,22 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream {
             ty,
         } = &field.field;
 
+        let mut ty = ty.to_token_stream();
+
+        // Replace lifetime for self-referential fields.
+        if !field.captures.is_empty() {
+            // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt`
+            // implementation and thus may be used inside `Erase`.
+            ty = quote!((#ty,));
+
+            for borrow in field.captures.iter().rev() {
+                let lt = &borrow.lifetime;
+                ty = quote!(for<#lt> fn(&#lt()) -> #ty);
+            }
+
+            ty = quote!(::pin_init::__internal::Erase<#ty>);
+        };
+
         quote! {
            #(#attrs)* #vis #ident #colon_token #ty
         }
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index cba53b8c3c94..0a8247473cbc 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -385,3 +385,80 @@ unsafe fn __init(self, _slot: *mut T) -> Result<(), ()> {
         Err(())
     }
 }
+/// Polyfill of `FnOnce` trait to be able to reference output via associated type.
+pub trait FnOutput<Args> {
+    type Output;
+}
+
+macro_rules! impl_fn_output {
+    () => {};
+    ($ret:ident, $($arg:ident,)*) => {
+        impl<This, $ret, $($arg,)*> FnOutput<($($arg,)*)> for This
+        where
+            This: FnOnce($($arg,)*) -> $ret,
+        {
+            type Output = $ret;
+        }
+        impl_fn_output!($($arg,)*);
+    };
+}
+
+impl_fn_output!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,);
+
+/// Lifetime erasure facility.
+///
+/// Say we have `exists<'a, 'b> Foo<'a, 'b>` and we want to store it. There's no concrete
+/// lifetimes we can use, so we want to erase the lifetime.
+///
+/// Such erasure can be encoded as
+/// `Erased<for<'a> fn(&'a ()) -> for<'b> fn(&'b()) -> (Foo<'a, 'b>,)`.
+///
+/// This can be considered the stable version of Rust's `unsafe_binder` feature, without the
+/// no-drop-glue requirement.
+#[repr(transparent)]
+#[allow(private_bounds)]
+pub struct Erase<F: EraseLt>(F::Erased);
+
+/// Helper trait to resolve the erased lifetime.
+trait EraseLt {
+    type Erased;
+}
+
+impl<T> EraseLt for (T,) {
+    type Erased = T;
+}
+
+impl<T> EraseLt for T
+where
+    T: for<'a> FnOutput<(&'a (),), Output: EraseLt>,
+{
+    type Erased = <<T as FnOutput<(&'static (),)>>::Output as EraseLt>::Erased;
+}
+
+// The default `Send` and `Sync` are not sufficient, because one can use lifetime specialization to
+// implement `Send` or `Sync` for a concrete instance of lifetime. Use HRTB to ensure that the type
+// will only implement `Send` or `Sync` if it's implemented for *all* erased lifetimes.
+
+// SAFETY: Trivial, no lifetime to erase.
+unsafe impl<T: Send> Send for Erase<(T,)> {}
+
+// SAFETY: If we erased a lifetime, then the type needs to be `Send` for across *all* that
+// lifetimes.
+unsafe impl<F: EraseLt> Send for Erase<F>
+where
+    F: for<'a> FnOutput<(&'a (),), Output: EraseLt>,
+    for<'a> Erase<<F as FnOutput<(&'a (),)>>::Output>: Send,
+{
+}
+
+// SAFETY: Trivial, no lifetime to erase.
+unsafe impl<T: Sync> Sync for Erase<(T,)> {}
+
+// SAFETY: If we erased a lifetime, then the type needs to be `Sync` for across *all* that
+// lifetimes.
+unsafe impl<F: EraseLt> Sync for Erase<F>
+where
+    F: for<'a> FnOutput<(&'a (),), Output: EraseLt>,
+    for<'a> Erase<<F as FnOutput<(&'a (),)>>::Output>: Sync,
+{
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 04/20] rust: pin-init: internal: pin_data: pin borrowed fields with wrapper
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (2 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 03/20] rust: pin-init: internal: pin_data: rewrite fields that borrow others Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 05/20] rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle Gary Guo
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

For fields that are borrowed, a mutable reference to the struct no longer
mean that it has the permission to access these fields. Therefore, the
memory that they refer to must be pinned.

Wrap these fields inside a `Borrowed` struct which pins it. They may be
accessed directly (if they're not themselves referencing other struct
fields), so implement a `Deref`.

As such fields are always pinned, there is no need to generate a
conditional `Unpin` implementations that implements `Unpin` when all fields
are. Simply generate a never satisfiable `Unpin` implementation to prevent
user from adding their own.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 19 +++++++++++++++++++
 rust/pin-init/src/__internal.rs        | 19 +++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 36678843251c..a3e492c455da 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -433,6 +433,10 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream {
             ty = quote!(::pin_init::__internal::Erase<#ty>);
         };
 
+        if field.borrowed.is_some() {
+            ty = quote!(::pin_init::__internal::Borrowed<#ty>);
+        }
+
         quote! {
            #(#attrs)* #vis #ident #colon_token #ty
         }
@@ -468,6 +472,20 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream {
         .map(|x| &x.predicates)
         .unwrap_or(const { &Punctuated::new() });
 
+    if info.self_referential {
+        // Self-referential structs must always be pinned.
+        return quote! {
+            #[doc(hidden)]
+            impl #impl_generics ::core::marker::Unpin for #ident #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>.
+                for<'__dummy> ::core::marker::PhantomPinned: ::core::marker::Unpin,
+                #predicates
+            {}
+        };
+    }
+
     let pinned_fields = info.fields.iter().filter(|f| f.pinned).map(|f| {
         let ident = f.member.as_ident();
         let ty = &f.field.ty;
@@ -475,6 +493,7 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream {
             #ident: #ty
         )
     });
+
     quote! {
         // This struct will be used for the unpin analysis. It is needed, because only structurally
         // pinned fields are relevant whether the struct should implement `Unpin`.
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index 0a8247473cbc..74dc23506d97 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -5,6 +5,9 @@
 //! These items must not be used outside of this crate and the pin-init-internal crate located at
 //! `../internal`.
 
+use core::marker::PhantomPinned;
+use core::ops::Deref;
+
 use super::*;
 
 /// Zero-sized type used to mark a type as invariant.
@@ -462,3 +465,19 @@ unsafe impl<F: EraseLt> Sync for Erase<F>
     for<'a> Erase<<F as FnOutput<(&'a (),)>>::Output>: Sync,
 {
 }
+
+/// Wrapper for borrowed fields.
+///
+/// This should be switched to `UnsafePinned` when it is stable.
+/// NOTE: This type needs to be covariant; Rust's 1.89+'s `UnsafePinned` is invariant.
+#[repr(transparent)]
+pub struct Borrowed<T: ?Sized>(PhantomPinned, T);
+
+impl<T: ?Sized> Deref for Borrowed<T> {
+    type Target = T;
+
+    #[inline(always)]
+    fn deref(&self) -> &T {
+        &self.1
+    }
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 05/20] rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (3 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 04/20] rust: pin-init: internal: pin_data: pin borrowed fields with wrapper Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 06/20] rust: pin-init: internal: pin_data: self-referential drop order checks Gary Guo
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Lifetimes not needed by drop glue are considered by Rust's drop check to be
considered `#[may_dangle]`. In case for a self-referential struct, we may
have fields which need lifetime of borrowed fields in their drop glue, so
compiler's automatic check is insufficient.

Code like this:

    #[pin_data]
    struct SelfRef<'a> {
        borrow: PrintOnDrop<&'owner str>,
        owner: &'a str,
    }

may access `owner` during the drop, however Rust will determine that since
`'a` only is used in `owner`, the `'a` lifetime may dangle during drop.

This is undesirable for pin-init self references, because `&'a str` could
be coerced to `&'owner str` and this could further coerce if there're
implied outlives, e.g. `&'earlier_field &'owner ()` would allow `&'owner
str` to further coerce to `&'earlier_field`.

Thus, if any self-referential field require field lifetime access in `Drop`
impl, we would need to ensure that the all generic parameters visible by
self-referential fields would strictly outlive the struct. And this can be
done by a simple `Drop` impl that does nothing. Without a dropck eye patch,
presence of `Drop` impl, albeit empty, tells the drop check that the strict
outlive relation is needed.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/src/__internal.rs | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index 74dc23506d97..32bd6d8c39a1 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -473,6 +473,35 @@ unsafe impl<F: EraseLt> Sync for Erase<F>
 #[repr(transparent)]
 pub struct Borrowed<T: ?Sized>(PhantomPinned, T);
 
+// Lifetimes not needed by drop glue are considered by Rust's drop check to be considered
+// `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of
+// borrowed fields in their drop glue, so compiler's automatic check is insufficient.
+//
+// Code like this:
+// ```
+// #[pin_data]
+// struct SelfRef<'a> {
+//     borrow: PrintOnDrop<&'owner str>,
+//     owner: &'a str,
+// }
+// ```
+// may access `owner` during the drop, however Rust will determine that since `'a` only is used in
+// `owner`, the `'a` lifetime may dangle during drop.
+//
+// This is undesirable for pin-init self references, because `&'a str` could be coerecd to
+// `&'owner str` and this could further coerce if there're implied outlives, e.g.
+// `&'earlier_field &'owner ()` would allow `&'owner str` to further coerce to `&'earlier_field`.
+//
+// Thus, if any self-referential field require field lifetime access in `Drop` impl, we would need
+// to ensure that the all generic parameters visible by self-referential fields would strictly
+// outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a
+// dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict
+// outlive relation is needed.
+impl<T: ?Sized> Drop for Borrowed<T> {
+    #[inline(always)]
+    fn drop(&mut self) {}
+}
+
 impl<T: ?Sized> Deref for Borrowed<T> {
     type Target = T;
 

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 06/20] rust: pin-init: internal: pin_data: self-referential drop order checks
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (4 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 05/20] rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 07/20] rust: pin-init: internal: pin_data: check covariance of self-referential fields Gary Guo
                   ` (13 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Check drop order to ensure that usage of lifetime inside self-referential
struct is consistent with the order that the fields will dropped in drop
glue.

First, fields are checked according to their index to ensure that if `a`
borrows from `b`, `b` must outlive `a`. This is simple and produces a very
good diagnostic when misused.

Lifetime bounds can also be indirectly crafted with implied bounds that
make fields well-formed. For example, in this struct

    struct Foo {
        x: &'b &'a (),
        a: String,
        y: PrintOnDrop<&'b str>,
        b: String,
    }

`&'b &'a ()` will imply that `a` outlive `b`, which is inconsistent with
the actual drop order. For this case, create a `__drop_order_check`
function with field lifetimes and outlive relationship of them as generic
parameter, and ask Rust to prove that the types are well-formed inside the
generated function, to ensure that the bad implied bounds cannot happen.

The `__drop_order_check` also need to correlate lifetimes or types captured
by generics and the field lifetimes. Do this by inserting outlive bounds
when a field mentions a specific type or lifetime parameter.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 212 ++++++++++++++++++++++++++++++++-
 rust/pin-init/internal/src/util.rs     |  65 +++++++++-
 2 files changed, 268 insertions(+), 9 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index a3e492c455da..ec0b1aeefe7f 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -2,8 +2,8 @@
 
 use std::collections::{BTreeMap, BTreeSet};
 
-use proc_macro2::TokenStream;
-use quote::{format_ident, quote, ToTokens};
+use proc_macro2::{Span, TokenStream};
+use quote::{format_ident, quote, quote_spanned, ToTokens};
 use syn::{
     parse::{End, Nothing, Parse},
     parse_quote, parse_quote_spanned,
@@ -11,8 +11,8 @@
     spanned::Spanned,
     visit::Visit,
     visit_mut::VisitMut,
-    Field, Fields, Generics, Ident, Index, Item, ItemStruct, Lifetime, Member, PathSegment, Type,
-    TypePath,
+    Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam,
+    Member, PathSegment, Type, TypePath,
 };
 
 use crate::{
@@ -115,14 +115,19 @@ struct FieldInfo {
     pinned: bool,
     borrowed: Option<BorrowedInfo>,
     captures: BTreeSet<Capture>,
+    generic_lt_captures: BTreeSet<Lifetime>,
+    generic_ty_captures: BTreeSet<Ident>,
 }
 
 struct StructInfo {
     args: Args,
     struct_: ItemStruct,
     fields: Vec<FieldInfo>,
+    field_idx_map: BTreeMap<Ident, usize>,
     is_tuple_struct: bool,
     self_referential: bool,
+    /// Field lifetime generics.
+    field_lts: Generics,
 }
 
 pub(crate) fn expand_with_cfg(
@@ -215,6 +220,8 @@ fn expand(
     // Collect all bound lifetimes from generics.
     let bound_lifetimes: BTreeSet<&Lifetime> =
         struct_.generics.lifetimes().map(|x| &x.lifetime).collect();
+    // Collect all type parameters from generics.
+    let type_params: BTreeSet<&Ident> = struct_.generics.type_params().map(|x| &x.ident).collect();
     // Collect all fields.
     let field_idx_map: BTreeMap<Ident, usize> = struct_
         .fields
@@ -248,6 +255,9 @@ fn expand(
             let mut captures = BTreeSet::new();
             let wildcard_variance = Variance::default();
 
+            let mut generic_lt_captures = BTreeSet::new();
+            let mut generic_ty_captures = BTreeSet::new();
+
             // Infer lifetime based on the field referenced.
             // Bound lifetimes from struct generics take priority.
             //
@@ -261,7 +271,12 @@ fn expand(
             // would not be inferred as self-referential because `'a` is already bound by the
             // struct generics.
             Lifetime::visitor(|lt| {
-                if bound_lifetimes.contains(lt) || captures.contains(lt) {
+                if bound_lifetimes.contains(lt) {
+                    generic_lt_captures.insert(lt.clone());
+                    return;
+                }
+
+                if captures.contains(lt) {
                     return;
                 }
 
@@ -284,12 +299,21 @@ fn expand(
                 implicitly_borrowed.insert(capture.lifetime.ident.clone());
             }
 
+            GenericParam::maybe_type_params_visitor(|ident| {
+                if type_params.contains(ident) {
+                    generic_ty_captures.insert(ident.clone());
+                }
+            })
+            .visit_type(&field.ty);
+
             FieldInfo {
                 field,
                 member,
                 pinned,
                 borrowed: None,
                 captures,
+                generic_lt_captures,
+                generic_ty_captures,
             }
         })
         .collect();
@@ -322,6 +346,58 @@ fn expand(
     })
     .visit_generics(&struct_.generics);
 
+    // Create a lifetime parameter for each field.
+    let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect();
+    let mut field_lts = Generics {
+        lt_token: None,
+        params: borrowed_fields
+            .iter()
+            .map(|borrowed| {
+                GenericParam::Lifetime(LifetimeParam {
+                    attrs: Vec::new(),
+                    lifetime: borrowed.lifetime.clone(),
+                    colon_token: None,
+                    bounds: Default::default(),
+                })
+            })
+            .collect(),
+        gt_token: None,
+        where_clause: None,
+    };
+
+    // Insert necessary bounds to make types well-formed.
+    for field in fields.iter() {
+        let Some(borrowed) = &field.borrowed else {
+            continue;
+        };
+        let field_lt = &borrowed.lifetime;
+
+        // For each borrowed field that borrows from other fields, we need to insert outlive bounds.
+        for capture in &field.captures {
+            let lt = &capture.lifetime;
+            field_lts
+                .make_where_clause()
+                .predicates
+                .push(parse_quote!(#lt: #field_lt));
+        }
+
+        // For each borrowed field that references a generic, we also need to insert their outlive
+        // bounds so they can refer to generics.
+        for lt in field.generic_lt_captures.iter() {
+            field_lts
+                .make_where_clause()
+                .predicates
+                .push(parse_quote!(#lt: #field_lt));
+        }
+
+        for ty in field.generic_ty_captures.iter() {
+            field_lts
+                .make_where_clause()
+                .predicates
+                .push(parse_quote!(#ty: #field_lt));
+        }
+    }
+
     struct_.fields = Fields::Unit;
     let info = StructInfo {
         self_referential: fields
@@ -330,7 +406,9 @@ fn expand(
         args,
         struct_,
         fields,
+        field_idx_map,
         is_tuple_struct,
+        field_lts,
     };
 
     for field in &info.fields {
@@ -356,6 +434,7 @@ fn expand(
     let struct_def = generate_struct_def(&info);
     let unpin_impl = generate_unpin_impl(&info);
     let drop_impl = generate_drop_impl(&info);
+    let drop_order_check = generate_drop_order_check(dcx, &info);
     let projections = generate_projections(&info);
     let the_pin_data = generate_the_pin_data(&info);
 
@@ -364,6 +443,7 @@ fn expand(
         // We put the rest into this const item, because it then will not be accessible to anything
         // outside.
         const _: () = {
+            #drop_order_check
             #projections
             #the_pin_data
             #unpin_impl
@@ -568,6 +648,128 @@ impl #impl_generics
     }
 }
 
+fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStream {
+    let ItemStruct {
+        ident: struct_name,
+        generics,
+        ..
+    } = &info.struct_;
+
+    // If the struct is not self-referential then we can just skip.
+    if !info.self_referential {
+        return quote!();
+    }
+
+    // Make sure fields are dropped earlier than the fields that they borrow.
+    for (i, field) in info.fields.iter().enumerate() {
+        let ident = field.member.as_ident();
+        for capture in &field.captures {
+            let borrowed_field = &capture.lifetime.ident;
+
+            if let Some(&borrowed_idx) = info.field_idx_map.get(borrowed_field) {
+                if i == borrowed_idx {
+                    // We need a strict outlive relationship, in case the lifetime is needed by the
+                    // field's drop glue.
+                    dcx.error(
+                        borrowed_field,
+                        format!("field `{ident}` cannot borrow from itself"),
+                    );
+                } else if i > borrowed_idx {
+                    dcx.error(
+                        borrowed_field,
+                        format!("field `{ident}` borrows `{borrowed_field}`, but drops later"),
+                    );
+                }
+            }
+        }
+    }
+
+    // The check above is necessary, but not sufficient.
+    //
+    // Consider this case:
+    // ```
+    // struct Foo {
+    //     x: &'b &'a (),
+    //     a: String,
+    //     y: PrintOnDrop<&'b str>,
+    //     b: String,
+    // }
+    // ```
+    // we need to ensure that `b` will strictly outlive `a`.
+    //
+    // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is
+    // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this
+    // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust
+    // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str`
+    // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a`
+    // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a
+    // use-after-free!
+    //
+    // Therefore, we must ensure the types contained within the struct has their implied bound being
+    // consistent with the actual lifetime relationship. We create a `__drop_order_check` function,
+    // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types
+    // are wellformed, given the bounds that we understand.
+
+    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]);
+
+    let (_, ty_generics, _) = generics.split_for_impl();
+    let (impl_generics_with_field_lt, _, whr_with_field_lt) =
+        generics_with_field_lt.split_for_impl();
+
+    // Prove the wellformedness of struct fields with regarding to the bounds of
+    // `__drop_order_check`.
+    //
+    // Consider this case:
+    // ```
+    // struct Foo {
+    //     x: &'b &'a (),
+    //     a: String,
+    //     y: PrintOnDrop<&'b str>,
+    //     b: String,
+    // }
+    // ```
+    // we need to ensure that `b` will strictly outlive `a`.
+    //
+    // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is
+    // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this
+    // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust
+    // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str`
+    // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a`
+    // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a
+    // use-after-free!
+    //
+    // Rust needs to *prove* the wellformedness of the type below, taking into account only the
+    // explicitly defined bounds plus the bounds implied by the lifetime-erased struct (but not
+    // the full implied bound between the field lifetimes).
+    let wf_proofs = info.fields.iter().rev().map(|f| {
+        let ty = &f.field.ty;
+        let ident = f.member.as_ident();
+        if let Some(borrowed) = &f.borrowed {
+            let lt = &borrowed.lifetime;
+            quote!(
+                let #ident: &#lt mut #ty = loop {};
+            )
+        } else {
+            quote!(
+                let #ident: #ty = loop {};
+            )
+        }
+    });
+
+    let struct_span = struct_name.span().resolved_at(Span::mixed_site());
+    quote_spanned! {struct_span =>
+        #[allow(non_snake_case, unused)]
+        fn __drop_order_check #impl_generics_with_field_lt (
+            // This must be present so the function can *assume* the implied bounds on the erased
+            // struct. For example, if the struct has `&'a T`, Rust will infer `T: 'a`; we still
+            // want to assume these bounds as they are not relevant to the field lifetimes.
+            _: &#struct_name #ty_generics,
+        ) #whr_with_field_lt {
+            #(#wf_proofs)*
+        }
+    }
+}
+
 fn generate_projections(info: &StructInfo) -> TokenStream {
     let ItemStruct {
         vis,
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index 67ebb333710f..f19712a46a30 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -5,7 +5,8 @@
 use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, ToTokens};
 use syn::{
-    visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, Token,
+    visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member,
+    Token, TypePath,
 };
 
 use crate::DiagCtxt;
@@ -85,6 +86,7 @@ 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>);
+pub(crate) struct CombinedWhereClauses<'a>(&'a CombinedGenerics<'a>);
 
 impl CombinedGenerics<'_> {
     pub(crate) fn split_for_impl(
@@ -92,10 +94,13 @@ pub(crate) fn split_for_impl(
     ) -> (
         CombinedImplGenerics<'_>,
         CombinedTypeGenerics<'_>,
-        // A stub type so `split_for_impl` signature matches that of `syn`'s.
-        impl Sized,
+        CombinedWhereClauses<'_>,
     ) {
-        (CombinedImplGenerics(self), CombinedTypeGenerics(self), ())
+        (
+            CombinedImplGenerics(self),
+            CombinedTypeGenerics(self),
+            CombinedWhereClauses(self),
+        )
     }
 }
 
@@ -242,6 +247,31 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
     }
 }
 
+impl ToTokens for CombinedWhereClauses<'_> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.0
+             .0
+            .iter()
+            .filter_map(|x| Some(x.where_clause.as_ref()?.where_token))
+            .next_back()
+            .unwrap_or_default()
+            .to_tokens(tokens);
+
+        let comma: Token![,] = Default::default();
+
+        for generics in self.0 .0.iter() {
+            let Some(where_clause) = &generics.where_clause else {
+                continue;
+            };
+
+            where_clause.predicates.to_tokens(tokens);
+            if !where_clause.predicates.empty_or_trailing() {
+                comma.to_tokens(tokens);
+            }
+        }
+    }
+}
+
 pub(crate) trait LifetimeExt {
     /// Get a visitor that call the provided function for all unbound lifetimes.
     fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>;
@@ -329,3 +359,30 @@ fn visit_type_bare_fn(&mut self, bare_fn: &'a syn::TypeBareFn) {
         });
     }
 }
+
+pub(crate) trait GenericParamExt {
+    fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a>;
+}
+
+impl GenericParamExt for GenericParam {
+    fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a> {
+        struct TypeParamVisitor<F>(F);
+
+        impl<'a, F> Visit<'a> for TypeParamVisitor<F>
+        where
+            F: FnMut(&'a Ident),
+        {
+            fn visit_type_path(&mut self, ty: &'a TypePath) {
+                if ty.qself.is_none()
+                    && ty.path.leading_colon.is_none()
+                    && ty.path.segments[0].arguments.is_none()
+                {
+                    (self.0)(&ty.path.segments[0].ident);
+                }
+                syn::visit::visit_type_path(self, ty);
+            }
+        }
+
+        TypeParamVisitor(f)
+    }
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 07/20] rust: pin-init: internal: pin_data: check covariance of self-referential fields
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (5 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 06/20] rust: pin-init: internal: pin_data: self-referential drop order checks Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 08/20] rust: pin-init: internal: pin_data: implement initialization of borrowed structs Gary Guo
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

We implicitly infer covariance for fields that self-references. This needs
to be checked to ensure that the fields are really covariant, so the rest
of expansion code can rely on this fact.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 75 +++++++++++++++++++++++++++++++++-
 rust/pin-init/internal/src/util.rs     | 24 ++++++++++-
 2 files changed, 96 insertions(+), 3 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index ec0b1aeefe7f..6a29ec358c30 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -76,7 +76,6 @@ enum Variance {
 }
 
 /// Information about field lifetimes captured in a type.
-#[expect(unused)]
 struct Capture {
     variance: Variance,
     /// Lifetime to be captured.
@@ -435,6 +434,7 @@ fn expand(
     let unpin_impl = generate_unpin_impl(&info);
     let drop_impl = generate_drop_impl(&info);
     let drop_order_check = generate_drop_order_check(dcx, &info);
+    let variance_check = generate_variance_check(&info);
     let projections = generate_projections(&info);
     let the_pin_data = generate_the_pin_data(&info);
 
@@ -444,6 +444,7 @@ fn expand(
         // outside.
         const _: () = {
             #drop_order_check
+            #variance_check
             #projections
             #the_pin_data
             #unpin_impl
@@ -770,6 +771,78 @@ fn __drop_order_check #impl_generics_with_field_lt (
     }
 }
 
+/// Produce variance checks, so we can ensure that the variance of lifetimes captured by field types
+/// actually match our expectation.
+fn generate_variance_check(info: &StructInfo) -> TokenStream {
+    if !info.self_referential {
+        return quote!();
+    }
+
+    let mut checks = Vec::new();
+
+    for f in info.fields.iter() {
+        let covariant_captures: Vec<_> = f
+            .captures
+            .iter()
+            .filter(|b| b.variance == Variance::Covariant)
+            .map(|b| &b.lifetime)
+            .collect();
+        if covariant_captures.is_empty() {
+            continue;
+        }
+
+        let ident = f.member.as_ident();
+        // Use the span of type for better error message.
+        let span = f.field.ty.span().resolved_at(Span::mixed_site());
+
+        let other_field_lifetimes = Generics {
+            lt_token: None,
+            params: f
+                .captures
+                .iter()
+                .filter(|b| b.variance != Variance::Covariant)
+                .map(|b| GenericParam::Lifetime(LifetimeParam::new(b.lifetime.clone())))
+                .collect(),
+            gt_token: None,
+            where_clause: None,
+        };
+
+        let long = Lifetime::new("'__long", span);
+        let long_ty = f
+            .field
+            .ty
+            .replace_lifetimes(&covariant_captures, &vec![&long; covariant_captures.len()]);
+
+        let short = Lifetime::new("'__short", span);
+        let short_ty = f
+            .field
+            .ty
+            .replace_lifetimes(&covariant_captures, &vec![&short; covariant_captures.len()]);
+
+        let check_name = format_ident!("__{ident}_covariance", span = span);
+
+        // Add `<'__long: '__short, 'short>` as additional generics.
+        let covariance_check_generics = parse_quote!(<#long: #short, #short>);
+        let combined_generics = CombinedGenerics(vec![
+            &covariance_check_generics,
+            &other_field_lifetimes,
+            &info.struct_.generics,
+        ]);
+        let (combined_impl_generics, _, whr) = combined_generics.split_for_impl();
+
+        checks.push(quote_spanned!(span =>
+            // Emit a check to ensure the type is *really* covariant for soundness.
+            fn #check_name #combined_impl_generics (long: #long_ty) -> #short_ty #whr {
+                long
+            }
+        ));
+    }
+
+    quote!(
+        #(#checks)*
+    )
+}
+
 fn generate_projections(info: &StructInfo) -> TokenStream {
     let ItemStruct {
         vis,
diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index f19712a46a30..59054f5934fb 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -5,8 +5,8 @@
 use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, ToTokens};
 use syn::{
-    visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member,
-    Token, TypePath,
+    parse_quote, visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime,
+    Member, Token, Type, TypePath,
 };
 
 use crate::DiagCtxt;
@@ -386,3 +386,23 @@ fn visit_type_path(&mut self, ty: &'a TypePath) {
         TypeParamVisitor(f)
     }
 }
+
+pub(crate) trait TypeExt {
+    fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type;
+}
+
+impl TypeExt for Type {
+    fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type {
+        if needle.is_empty() {
+            return self.clone();
+        }
+
+        parse_quote!(
+            <
+                for<#(#needle,)*> fn(#(&#needle (),)*) -> #self
+                    as
+                ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)>
+            >::Output
+        )
+    }
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 08/20] rust: pin-init: internal: pin_data: implement initialization of borrowed structs
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (6 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 07/20] rust: pin-init: internal: pin_data: check covariance of self-referential fields Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 09/20] rust: pin-init: internal: pin_data: project self-referential fields Gary Guo
                   ` (11 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

We now have the checks to ensure that lifetime relations are what is
expected, we can generate the slot projections in `generate_pin_data` so
self-referential struct can be implemented.

New slot and guard types are defined (`SelfRefSlot` and `SelfRefDropGuard`)
which gives the generated let bindings longer lifetime than the guard
themselves.

Have `__make_closure` take `data` back as an argument. This gives
`#[pin_data]` an opportunity to change the type to add lifetimes.
Higher-ranked trait bound on `__make_closure` is used to ensure that the
initialization closure cannot make arbitrary assumptions of those
lifetimes.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/init.rs     |  42 ++++++--
 rust/pin-init/internal/src/pin_data.rs | 114 +++++++++++++++++++---
 rust/pin-init/src/__internal.rs        | 169 ++++++++++++++++++++++++++++++++-
 rust/pin-init/src/lib.rs               |   1 +
 4 files changed, 305 insertions(+), 21 deletions(-)

diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index 80e4dc068d2e..ded2b5283376 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -288,8 +288,10 @@ fn assert_zeroable<T: ?::core::marker::Sized>(_: *mut T)
         },
     };
     // `mixed_site` ensures that the data is not accessible to the user-controlled code.
-    let init_fields = init_fields(&fields, pinned);
+    let init_fields = make_field_init(&fields, pinned, false);
+    let drop_check = make_field_init(&fields, pinned, true);
     let field_check = make_field_check(&fields, init_kind, &path);
+
     Ok(quote_spanned! { Span::mixed_site() => {
         // Get the data about fields from the supplied type.
         let data = {
@@ -303,17 +305,29 @@ fn assert_zeroable<T: ?::core::marker::Sized>(_: *mut T)
 
         // Ensure that `data` really is of type `data` and help with type inference:
         let init = data.__make_closure::<_, #error>(
-            move |slot| {
+            move |slot, data_lt| {
                 #zeroable_check
                 #this
-                #init_fields
+                // Generate init twice, which is mostly identical except for lifetimes.
+                if true {
+                    // In this path, we use the field lifetime from HRTB to prevent environment
+                    // lifetime from entering the fields, and to ensure that the dependency of
+                    // fields is consistent with the field drop of the struct.
+                    #init_fields
+                } else {
+                    // In this path, we use local lifetime, to make sure that if initialization
+                    // fails, the destructor execution will not cause lifetime issues. This is
+                    // separate as implied bounds between field lifetimes can be inconsistent with
+                    // that of the drop.
+                    #drop_check
+                }
                 #field_check
                 // SAFETY: we are the `init!` macro that is allowed to call this.
                 Ok(unsafe { ::pin_init::__internal::InitOk::new() })
             }
         );
         let init = move |slot| -> ::core::result::Result<(), #error> {
-            init(slot).map(|__InitOk| ())
+            init(slot, data.__with_lt()).map(|__InitOk| ())
         };
         // SAFETY: TODO
         unsafe { ::pin_init::#init_from_closure::<_, #error>(init) }
@@ -360,7 +374,11 @@ 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 {
+fn make_field_init(
+    fields: &Punctuated<InitializerField, Token![,]>,
+    pinned: bool,
+    dropck: bool,
+) -> TokenStream {
     let mut forget_guards = vec![];
     let mut res = TokenStream::new();
     for InitializerField { attrs, kind } in fields {
@@ -388,13 +406,18 @@ fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -
         let span = Span::mixed_site().located_at(ident.span());
 
         let slot = if pinned {
+            let data = if !dropck {
+                quote_spanned!(span => data_lt)
+            } else {
+                quote_spanned!(span => data.__with_lt())
+            };
             quote_spanned! { span =>
                 // SAFETY:
                 // - `slot` is valid and properly aligned.
                 // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned.
                 // - `make_field_check` prevents `#member` from being used twice, therefore
                 //   `(*slot).#member` is exclusively accessed and has not been initialized.
-                (unsafe { data.#ident(slot) })
+                (unsafe { #data.#ident(slot) })
             }
         } else {
             quote_spanned! { span =>
@@ -455,6 +478,11 @@ fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -
 
         // A tuple field has no name that could be bound here (the `_0` identifiers are considered
         // implementation detail and not user-facing).
+        let let_binding_method = if !dropck {
+            format_ident!("let_binding", span = span)
+        } else {
+            format_ident!("let_binding_in_dropck", span = span)
+        };
         let binding = match member {
             Member::Named(ident) => quote_spanned! { span =>
                 #(#cfgs)*
@@ -462,7 +490,7 @@ fn init_fields(fields: &Punctuated<InitializerField, Token![,]>, pinned: bool) -
                 // struct field.
                 #[allow(unused_variables, non_snake_case)]
                 // Include `mut` so that `Pin<&mut T>` bindings can be reborrowed via `.as_mut()`.
-                let mut #ident = #guard.let_binding();
+                let mut #ident = #guard.#let_binding_method();
             },
             Member::Unnamed(_) => quote!(),
         };
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 6a29ec358c30..307d9b36078c 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -61,7 +61,6 @@ enum BorrowedKind {
 }
 
 /// Information about a borrowed field.
-#[expect(unused)]
 struct BorrowedInfo {
     kind: BorrowedKind,
     /// Field lifetime for this field.
@@ -974,12 +973,36 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
         generics,
         ..
     } = &info.struct_;
+
+    // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
+    // used with `for`.
+    let field_lts = CombinedGenerics(vec![&info.field_lts]);
+    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]);
+
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
+    let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
+    let (impl_generics_with_lt, ty_generics_with_field_lt, whr_with_field_lt) =
+        generics_with_field_lt.split_for_impl();
+
+    // Wrap each field in a `PhantomInvariant`. For borrowed fields, additionally
+    // use `&#lt mut #ty` so the `lt` becomes associated with `#ty` which deduces
+    // implied bounds.
+    let phantom_fields = info.fields.iter().map(|f| {
+        let ty = &f.field.ty;
+        let ident = f.member.as_ident();
+
+        if let Some(borrowed) = &f.borrowed {
+            let lt = &borrowed.lifetime;
+            quote!(
+                #ident: ::pin_init::__internal::PhantomInvariant<&#lt mut #ty>,
+            )
+        } else {
+            quote!(
+                #ident: ::pin_init::__internal::PhantomInvariant<#ty>,
+            )
+        }
+    });
 
-    // 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 = info
         .fields
         .iter()
@@ -992,6 +1015,30 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
             } else {
                 quote!(Unpinned)
             };
+
+            let (slot_ty, slot_arg) = match &f.borrowed {
+                None => (quote!(Slot), quote!()),
+                Some(BorrowedInfo {
+                    kind: BorrowedKind::Shared,
+                    lifetime,
+                }) => (
+                    // For borrowed fields, create a `SelfRefSlot`, which after initialization
+                    // turns into a `SelfRefDropGuard` instead of `DropGuard`.
+                    //
+                    // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T`
+                    // instead of `&'guard T` for let bindings; this allows it to be used to be
+                    // used to initialize other fields.
+                    //
+                    // The soundness of doing so relies on fact that `__make_init` requires a
+                    // higher-ranked trait bound on the closure. Within the closure (which is the
+                    // caller of the generated slot projection functions here), it can make no
+                    // assumptions on the lifetime except for those implied by the struct's bounds,
+                    // and we have validated them in `generate_drop_check`.
+                    quote!(SelfRefSlot),
+                    quote!(#lifetime,),
+                ),
+            };
+
             quote! {
                 /// # Safety
                 ///
@@ -1006,19 +1053,54 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
                 #vis unsafe fn #field_name(
                     self,
                     slot: *mut #struct_name #ty_generics,
-                ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> {
+                ) -> ::pin_init::__internal::#slot_ty<
+                    #slot_arg ::pin_init::__internal::#pin_marker, #ty
+                > {
+                    // CAST: `as _` is needed to convert types wrapped inside `SelfRef`.
                     // SAFETY:
                     // - 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).#member) }
+                    // - If `#slot_ty` is `SelfRefSlot`, the lifetime `#lt` represents that of the
+                    //   field.
+                    unsafe { ::pin_init::__internal::#slot_ty::new(&raw mut (*slot).#member as _) }
                 }
             }
         })
         .collect::<TokenStream>();
+
     quote! {
-        // We declare this struct which will host all of the projection function for our type. It
-        // will be invariant over all generic parameters which are inherited from the struct.
+        // We declare this struct which will host all of the projection function for our type.
+        #[doc(hidden)]
+        #[allow(non_snake_case)]
+        #vis struct __PinDataLt #generics_with_field_lt
+            #whr_with_field_lt
+        {
+            #(#phantom_fields)*
+            __pin_phantom: ::core::marker::PhantomData<#struct_name #ty_generics>,
+        }
+
+        impl #impl_generics_with_lt ::core::clone::Clone for __PinDataLt #ty_generics_with_field_lt
+            #whr_with_field_lt
+        {
+            fn clone(&self) -> Self { *self }
+        }
+
+        impl #impl_generics_with_lt ::core::marker::Copy for __PinDataLt #ty_generics_with_field_lt
+            #whr_with_field_lt
+        {}
+
+        #[allow(dead_code)] // Some functions might never be used and private.
+        #[expect(clippy::missing_safety_doc)]
+        impl #impl_generics_with_lt __PinDataLt #ty_generics_with_field_lt
+            #whr_with_field_lt
+        {
+            #field_accessors
+        }
+
+        // Declare a type that serves as the entry point of interaction with the `pin_init!` macro.
+        // We use this type instead of defining methods directly on user's type to avoid possibility
+        // of name conflicts.
         #[doc(hidden)]
         #vis struct __ThePinData #generics
             #whr
@@ -1037,7 +1119,6 @@ impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics
             #whr
         {}
 
-        #[allow(dead_code)] // Some functions might never be used and private.
         impl #impl_generics __ThePinData #ty_generics
             #whr
         {
@@ -1045,13 +1126,20 @@ impl #impl_generics __ThePinData #ty_generics
             #[inline(always)]
             #vis fn __make_closure<__F, __E>(self, f: __F) -> __F
             where
-                __F: FnOnce(*mut #struct_name #ty_generics) ->
-                    ::core::result::Result<::pin_init::__internal::InitOk, __E>,
+                __F: for #field_lt_ty_generics ::core::ops::FnOnce(
+                    *mut #struct_name #ty_generics,
+                    __PinDataLt #ty_generics_with_field_lt
+                ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>,
             {
                 f
             }
 
-            #field_accessors
+            #[inline(always)]
+            #vis fn __with_lt #field_lts(self) -> __PinDataLt #ty_generics_with_field_lt {
+                // Generate a zeroed to avoid naming all fields.
+                // SAFETY: `__PinDataLt` only contains phantom fields.
+                unsafe { ::core::mem::zeroed() }
+            }
         }
 
         // SAFETY: We have added the correct projection functions above to `__ThePinData` and
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index 32bd6d8c39a1..df079e68ec1e 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -109,10 +109,15 @@ impl<T: ?Sized> InitData<T> {
     #[inline(always)]
     pub fn __make_closure<F, E>(self, f: F) -> F
     where
-        F: FnOnce(*mut T) -> Result<InitOk, E>,
+        F: FnOnce(*mut T, Self) -> Result<InitOk, E>,
     {
         f
     }
+
+    #[inline(always)]
+    pub fn __with_lt(self) -> Self {
+        self
+    }
 }
 
 /// Stack initializer helper type. Use [`stack_pin_init`] instead of this primitive.
@@ -322,6 +327,12 @@ pub fn let_binding(&mut self) -> &mut T {
         // SAFETY: Per type invariant.
         unsafe { &mut *self.ptr }
     }
+
+    /// Create a let binding for accessor use in dropck.
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> &mut T {
+        self.let_binding()
+    }
 }
 
 impl<T: ?Sized> DropGuard<Pinned, T> {
@@ -332,6 +343,12 @@ pub fn let_binding(&mut self) -> Pin<&mut T> {
         // pinned per type invariant.
         unsafe { Pin::new_unchecked(&mut *self.ptr) }
     }
+
+    /// Create a let binding for accessor use in dropck.
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> {
+        self.let_binding()
+    }
 }
 
 impl<P, T: ?Sized> Drop for DropGuard<P, T> {
@@ -342,6 +359,156 @@ fn drop(&mut self) {
     }
 }
 
+/// Represent an uninitialized field in a pinned struct that will be referenced by other fields.
+///
+/// # Invariants
+///
+/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory
+///   and will live longer than `'a`.
+/// - If `P` is `Pinned`, then `ptr` is structurally pinned.
+pub struct SelfRefSlot<'a, P, T: ?Sized> {
+    pub ptr: *mut T,
+    pub _phantom: PhantomData<(P, &'a mut T)>,
+}
+
+impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> {
+    /// # Safety
+    ///
+    /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed
+    ///   memory and will live longer than `'a`.
+    /// - If `P` is `Pinned`, then `ptr` is structurally pinned.
+    #[inline]
+    pub unsafe fn new(ptr: *mut T) -> Self {
+        // INVARIANT: Per safety requirement.
+        Self {
+            ptr,
+            _phantom: PhantomData,
+        }
+    }
+
+    /// Initialize the field by value.
+    #[inline]
+    pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T>
+    where
+        T: Sized,
+    {
+        // SAFETY: `self.ptr` is a valid and aligned pointer for write.
+        unsafe { self.ptr.write(value) }
+        // SAFETY:
+        // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant.
+        // - `*self.ptr` is initialized above and the ownership is transferred to the guard.
+        // - If `P` is `Pinned`, `self.ptr` is pinned.
+        unsafe { SelfRefDropGuard::new(self.ptr) }
+    }
+}
+
+impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> {
+    /// Initialize the field.
+    #[inline]
+    pub fn init<E>(self, init: impl Init<T, E>) -> Result<SelfRefDropGuard<'a, Unpinned, T>, E> {
+        // SAFETY:
+        // - `self.ptr` is valid and properly aligned.
+        // - when `Err` is returned, we also propagate the error without touching `slot`;
+        //   also `self` is consumed so it cannot be touched further.
+        unsafe { init.__init(self.ptr)? };
+
+        // SAFETY:
+        // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant.
+        // - `*self.ptr` is initialized above and the ownership is transferred to the guard.
+        Ok(unsafe { SelfRefDropGuard::new(self.ptr) })
+    }
+}
+
+impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> {
+    /// Initialize the field.
+    #[inline]
+    pub fn init<E>(self, init: impl PinInit<T, E>) -> Result<SelfRefDropGuard<'a, Pinned, T>, E> {
+        // SAFETY:
+        // - `ptr` is valid
+        // - when `Err` is returned, we also propagate the error without touching `ptr`;
+        //   also `self` is consumed so it cannot be touched further.
+        // - the drop guard will not hand out `&mut` (but only `Pin<&mut T>`) it has been dropped.
+        unsafe { init.__init(self.ptr)? };
+
+        // SAFETY:
+        // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant.
+        // - `*self.ptr` is initialized above and the ownership is transferred to the guard.
+        Ok(unsafe { SelfRefDropGuard::new(self.ptr) })
+    }
+}
+/// When a value of this type is dropped, it drops a `T`.
+///
+/// Can be forgotten to prevent the drop.
+///
+/// # Invariants
+///
+/// - `ptr` is valid, properly aligned and live longer than `'a`.
+/// - `*ptr` is initialized and owned by this guard.
+/// - if `P` is `Pinned`, `ptr` is pinned.
+pub struct SelfRefDropGuard<'a, P, T: ?Sized> {
+    ptr: *mut T,
+    phantom: PhantomData<(P, &'a mut T)>,
+}
+
+impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> {
+    /// Creates a drop guard and transfer the ownership of the pointer content.
+    ///
+    /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`].
+    ///
+    /// # Safety
+    ///
+    /// - `ptr` is valid, properly aligned and live longer than `'a`.
+    /// - `*ptr` is initialized, and the ownership is transferred to this guard.
+    /// - if `P` is `Pinned`, `ptr` is pinned.
+    #[inline]
+    pub unsafe fn new(ptr: *mut T) -> Self {
+        // INVARIANT: By safety requirement.
+        Self {
+            ptr,
+            phantom: PhantomData,
+        }
+    }
+}
+
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> {
+    /// Create a let binding for accessor use.
+    #[inline]
+    pub fn let_binding(&mut self) -> &'a T {
+        // SAFETY: Per type invariant.
+        unsafe { &*self.ptr }
+    }
+
+    /// Create a let binding for accessor use in dropck.
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> &T {
+        self.let_binding()
+    }
+}
+
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> {
+    /// Create a let binding for accessor use.
+    #[inline]
+    pub fn let_binding(&mut self) -> Pin<&'a T> {
+        // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized,
+        // exclusively accessible and pinned per type invariant.
+        unsafe { Pin::new_unchecked(&*self.ptr) }
+    }
+
+    /// Create a let binding for accessor use in dropck.
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> Pin<&T> {
+        self.let_binding()
+    }
+}
+
+impl<P, T: ?Sized> Drop for SelfRefDropGuard<'_, P, T> {
+    #[inline]
+    fn drop(&mut self) {
+        // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard.
+        unsafe { ptr::drop_in_place(self.ptr) }
+    }
+}
+
 /// Token used by `PinnedDrop` to prevent calling the function without creating this unsafely
 /// created struct. This is needed, because the `drop` function is safe, but should not be called
 /// manually.
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index d1ed0561bb27..9ffa76434310 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -923,6 +923,7 @@ macro_rules! assert_pinned {
             let data = <$ty as $crate::__internal::HasInitData>::__init_data();
             let data = $crate::__internal::HasPinData::__pin_data(data);
             _ = data
+                .__with_lt()
                 .$field(ptr)
                 .init($crate::__internal::AlwaysFail::<$field_ty>::new());
         };

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 09/20] rust: pin-init: internal: pin_data: project self-referential fields
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (7 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 08/20] rust: pin-init: internal: pin_data: implement initialization of borrowed structs Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 10/20] rust: pin-init: internal: pin_data: add `with_project` method Gary Guo
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

This adds the projection for fields that are shared borrowed or that
borrows other fields but is covariant. Both cases allow a shared reference
to be accessed.

No mutable references can be created for these cases for different reasons:
* For fields that are shared borrowed, aliasing restriction prevents
  creation of mutable reference
* For fields that borrow other fields, their proper type contains field
  lifetimes. These lifetimes cannot be made available in the returned
  `project` struct (because there is no way to represent existential
  lifetime in return position). For covariant types, it is possible to
  shorten these lifetimes to that of `&self`; but doing so requires the
  reference to also be covariant over the pointee type, so we cannot give
  out `&mut` as it is invariant over the pointee.

Due to field-referencing fields being wrapped inside `Erase`, the normal
accessor syntax stop working; create accessor methods for these fields
instead.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 109 ++++++++++++++++++++++++++++-----
 rust/pin-init/src/__internal.rs        |  12 ++++
 2 files changed, 107 insertions(+), 14 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 307d9b36078c..aa9a848c6670 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -12,7 +12,7 @@
     visit::Visit,
     visit_mut::VisitMut,
     Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam,
-    Member, PathSegment, Type, TypePath,
+    Member, PathSegment, Token, Type, TypePath,
 };
 
 use crate::{
@@ -849,7 +849,8 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         generics,
         ..
     } = &info.struct_;
-    let this_lt_generics: Generics = parse_quote!(<'__this>);
+    let this_lt = Lifetime::new("'__this", Span::mixed_site());
+    let this_lt_generics: Generics = parse_quote!(<#this_lt>);
     let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
 
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
@@ -860,33 +861,68 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
     let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info
         .fields
         .iter()
-        .map(|field| {
-            let Field { vis, ty, .. } = &field.field;
-            let member = &field.member;
+        .map(|f| {
+            let vis = &f.field.vis;
+            let ident = f.member.as_ident();
+            let member = &f.member;
             // The projection of a tuple struct is a tuple struct itself, so its fields are
             // positional and must not be named.
-            let name = (!info.is_tuple_struct).then(|| {
-                let ident = field.member.as_ident();
-                quote!(#ident:)
-            });
+            let name = (!info.is_tuple_struct).then(|| quote!(#ident:));
+
+            // if `f.ty` contains field lifetimes, which we need to replace them with shorter
+            // `'__this` lifetime as field lifetimes are not available in this context.
+            let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect();
+            let ty = f
+                .field
+                .ty
+                .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]);
+
+            // Fields sharedly borrowed by other fields can only be shared accessed. Fields that
+            // references other field and are covariant can also only be given shared reference
+            // as mutable reference is invariant.
+            let mut_token: Option<Token![mut]> = if f.borrowed.is_none() && f.captures.is_empty() {
+                Some(Default::default())
+            } else {
+                None
+            };
+
+            let mut accessor = quote!(&#mut_token #this.#member);
+            if !f.captures.is_empty() || f.borrowed.is_some() {
+                accessor = quote!(
+                    // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`.
+                    // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance.
+                    unsafe { ::core::mem::transmute::<_, &#mut_token #ty>(#accessor) }
+                )
+            }
 
-            if field.pinned {
+            if !f.captures.iter().all(|b| b.variance == Variance::Covariant) {
+                // If the type is not covariant, it must omitted, as projection shortens the
+                // lifetime to `'__this`.
                 (
                     quote!(
-                        #vis #name ::core::pin::Pin<&'__this mut #ty>,
+                        #vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>,
+                    ),
+                    quote!(
+                        #name ::pin_init::__internal::NotVisible::new(),
+                    ),
+                )
+            } else if f.pinned {
+                (
+                    quote!(
+                        #vis #name ::core::pin::Pin<&'__this #mut_token #ty>,
                     ),
                     quote!(
                         // SAFETY: this field is structurally pinned.
-                        #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) },
+                        #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) },
                     ),
                 )
             } else {
                 (
                     quote!(
-                        #vis #name &'__this mut #ty,
+                        #vis #name &'__this #mut_token #ty,
                     ),
                     quote!(
-                        #name &mut #this.#member,
+                        #name #accessor,
                     ),
                 )
             }
@@ -936,6 +972,49 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
             },
         )
     };
+
+    // For fields that references other fields, field access syntax stops working as they're wrapped
+    // behind `Erase` because their actual lifetime is not on the struct.
+    //
+    // Generate an accessor method for them.
+    let mut accessors = Vec::new();
+    for f in info.fields.iter() {
+        let ident = f.member.as_ident();
+        let member = &f.member;
+
+        if f.captures.is_empty() {
+            // They can be accessed normally, no accessor to be generated.
+            continue;
+        }
+
+        if f.captures.iter().all(|b| b.variance == Variance::Covariant) {
+            let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`.");
+            let vis = &f.field.vis;
+
+            // Use the span of type for better error message.
+            let span = f.field.ty.span().resolved_at(Span::mixed_site());
+
+            let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect();
+            let ty = f
+                .field
+                .ty
+                .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]);
+
+            accessors.push(quote_spanned!(span =>
+                #[doc = #f_doc]
+                #[inline]
+                #[allow(clippy::mut_from_ref)] // false positive when `&&mut` is returned.
+                #vis fn #ident<#this_lt>(&#this_lt self) -> &#this_lt #ty {
+                    // SAFETY: we have `Erased<..>` which we know is layout compatible with `f.ty`.
+                    // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance.
+                    unsafe { ::core::mem::transmute(&self.#member) }
+                }
+            ))
+        } else {
+            continue;
+        }
+    }
+
     quote! {
         #[doc = #docs]
         // Allow `non_snake_case` since the same warning will be emitted on
@@ -962,6 +1041,8 @@ impl #impl_generics #ident #ty_generics
                 let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
                 #projection_init
             }
+
+            #(#accessors)*
         }
     }
 }
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index df079e68ec1e..ddd99e705c93 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -5,6 +5,8 @@
 //! These items must not be used outside of this crate and the pin-init-internal crate located at
 //! `../internal`.
 
+#![expect(clippy::new_without_default, reason = "private API")]
+
 use core::marker::PhantomPinned;
 use core::ops::Deref;
 
@@ -677,3 +679,13 @@ fn deref(&self) -> &T {
         &self.1
     }
 }
+
+/// An alias of `PhantomData` but with a name to aid user in case of misuse.
+pub struct NotVisible<T: ?Sized>(PhantomData<T>);
+
+impl<T: ?Sized> NotVisible<T> {
+    #[inline(always)]
+    pub fn new() -> Self {
+        Self(PhantomData)
+    }
+}

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 10/20] rust: pin-init: internal: pin_data: add `with_project` method
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (8 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 09/20] rust: pin-init: internal: pin_data: project self-referential fields Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 11/20] rust: pin-init: internal: pin_data: enable self-referential support Gary Guo
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

The `project` method needs to perform covariant coercion on covariant
fields, causing them to no longer being mutable. Implement a `with_project`
that does not require covariant coercion by using higher-ranked trait
bounds, thus allow the fields to be assignable inside the callback.

This mechanism can also be used to access non-covariant fields.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 141 +++++++++++++++++++++++++++++++--
 1 file changed, 136 insertions(+), 5 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index aa9a848c6670..d851f13a1099 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -851,10 +851,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
     } = &info.struct_;
     let this_lt = Lifetime::new("'__this", Span::mixed_site());
     let this_lt_generics: Generics = parse_quote!(<#this_lt>);
-    let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
 
+    // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
+    // used with `for`.
+    let field_lts = CombinedGenerics(vec![&info.field_lts]);
+    let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
+    let generics_with_this_field_lt =
+        CombinedGenerics(vec![&this_lt_generics, &info.field_lts, generics]);
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
+    let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
     let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl();
+    let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl();
 
     let this = format_ident!("this");
 
@@ -928,16 +935,78 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
             }
         })
         .collect();
-    let structurally_pinned_fields_docs = info
+
+    let (fields_decl_lt, fields_proj_lt): (Vec<_>, Vec<_>) = info
+        .fields
+        .iter()
+        .map(|f| {
+            let vis = &f.field.vis;
+            let ident = f.member.as_ident();
+            let member = &f.member;
+            let name = (!info.is_tuple_struct).then(|| quote!(#ident:));
+
+            let ty = &f.field.ty;
+
+            // Fields shared-referenced by other fields can only be shared accessed.
+            let mut_token: Option<Token![mut]> = if f.borrowed.is_none() {
+                Some(Default::default())
+            } else {
+                None
+            };
+
+            let mut accessor = quote!(&#mut_token #this.#member);
+            if !f.captures.is_empty() || f.borrowed.is_some() {
+                accessor = quote!(
+                    // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`.
+                    // We cannot include explicit type name here as the field lifetimes are nameable
+                    // in this context, so `for<'field_name> ..` would fail.
+                    unsafe { ::core::mem::transmute(#accessor) }
+                )
+            }
+
+            // In `with_project`, borrowed fields have their field lifetime available, so use it
+            // instead of `'__this`.
+            let lt = if f.borrowed.is_some() {
+                Lifetime::from_ident(&ident)
+            } else {
+                this_lt.clone()
+            };
+
+            if f.pinned {
+                (
+                    quote!(
+                        #vis #name ::core::pin::Pin<&#lt #mut_token #ty>,
+                    ),
+                    quote!(
+                        // SAFETY: this field is structurally pinned.
+                        #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) },
+                    ),
+                )
+            } else {
+                (
+                    quote!(
+                        #vis #name &#lt #mut_token #ty,
+                    ),
+                    quote!(
+                        #name #accessor,
+                    ),
+                )
+            }
+        })
+        .collect();
+
+    let structurally_pinned_fields_docs: Vec<_> = info
         .fields
         .iter()
         .filter(|f| f.pinned)
-        .map(|f| format!(" - {}", f.member.display_name()));
-    let not_structurally_pinned_fields_docs = info
+        .map(|f| format!(" - {}", f.member.display_name()))
+        .collect();
+    let not_structurally_pinned_fields_docs: Vec<_> = info
         .fields
         .iter()
         .filter(|f| !f.pinned)
-        .map(|f| format!(" - {}", f.member.display_name()));
+        .map(|f| format!(" - {}", f.member.display_name()))
+        .collect();
     let docs = format!(" Pin-projections of [`{ident}`]");
     let (projection_def, projection_init) = if info.is_tuple_struct {
         (
@@ -973,6 +1042,42 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         )
     };
 
+    let projection_lt = format_ident!("__ProjectionLt");
+    let (projection_lt_def, projection_lt_init) = if info.is_tuple_struct {
+        (
+            quote! {
+                #vis struct #projection_lt #generics_with_this_field_lt (
+                    #(#fields_decl_lt)*
+                    ::core::marker::PhantomData<&'__this mut #ident #ty_generics>,
+                ) #whr;
+            },
+            quote! {
+                #projection_lt(
+                    #(#fields_proj_lt)*
+                    ::core::marker::PhantomData,
+                )
+            },
+        )
+    } else {
+        (
+            quote! {
+                #vis struct #projection_lt #generics_with_this_field_lt
+                    #whr
+                {
+                    #(#fields_decl_lt)*
+                    ___pin_phantom_data:
+                        ::core::marker::PhantomData<&'__this mut #ident #ty_generics>,
+                }
+            },
+            quote! {
+                #projection_lt {
+                    #(#fields_proj_lt)*
+                    ___pin_phantom_data: ::core::marker::PhantomData,
+                }
+            },
+        )
+    };
+
     // For fields that references other fields, field access syntax stops working as they're wrapped
     // behind `Erase` because their actual lifetime is not on the struct.
     //
@@ -1023,6 +1128,13 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         #[doc(hidden)]
         #projection_def
 
+        #[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)]
+        #projection_lt_def
+
         impl #impl_generics #ident #ty_generics
             #whr
         {
@@ -1042,6 +1154,25 @@ impl #impl_generics #ident #ty_generics
                 #projection_init
             }
 
+            /// Pin-projects all fields of `Self` with proper lifetime.
+            ///
+            /// These fields are structurally pinned:
+            #(#[doc = #structurally_pinned_fields_docs])*
+            ///
+            /// These fields are **not** structurally pinned:
+            #(#[doc = #not_structurally_pinned_fields_docs])*
+            #[inline]
+            #vis fn with_project<'__this, R>(
+                self: ::core::pin::Pin<&'__this mut Self>,
+                f: impl for #field_lt_ty_generics ::core::ops::FnOnce(
+                    #projection_lt #ty_generics_with_this_field_lt
+                ) -> R,
+            ) -> R {
+                // SAFETY: we only give access to `&mut` for fields not structurally pinned.
+                let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
+                f(#projection_lt_init)
+            }
+
             #(#accessors)*
         }
     }

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 11/20] rust: pin-init: internal: pin_data: enable self-referential support
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (9 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 10/20] rust: pin-init: internal: pin_data: add `with_project` method Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 12/20] rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order Gary Guo
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Enable self-referential support, and add example and test cases.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/examples/selfref.rs      | 35 ++++++++++++++++++++++++++++++++++
 rust/pin-init/internal/src/pin_data.rs |  7 -------
 2 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs
new file mode 100644
index 000000000000..b5cdf96b6d1c
--- /dev/null
+++ b/rust/pin-init/examples/selfref.rs
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+#![allow(clippy::disallowed_names)]
+
+use pin_init::*;
+
+#[pin_data]
+struct SelfRef {
+    part: &'str str,
+    str: String,
+}
+
+fn use_self_ref() {
+    stack_pin_init!(let foo = pin_init!(SelfRef {
+        str: "hello world".to_owned(),
+        part: &str[..5],
+    }));
+
+    // Access via projection.
+    println!("{}", foo.as_mut().project().part);
+
+    // Access via accessor.
+    println!("{}", foo.part());
+
+    // Access via `with_project`, gives mutable reference.
+    foo.as_mut().with_project(|proj| {
+        *proj.part = &proj.str[5..];
+    });
+
+    println!("{}", foo.part());
+}
+
+fn main() {
+    use_self_ref();
+}
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index d851f13a1099..6b2c0b1f96b5 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -422,13 +422,6 @@ fn expand(
         }
     }
 
-    if info.self_referential {
-        dcx.error(
-            &info.struct_.ident,
-            "self-referential support is not fully implemented",
-        );
-    }
-
     let struct_def = generate_struct_def(&info);
     let unpin_impl = generate_unpin_impl(&info);
     let drop_impl = generate_drop_impl(&info);

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 12/20] rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (10 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 11/20] rust: pin-init: internal: pin_data: enable self-referential support Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 13/20] rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation Gary Guo
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Add the outlive relations per field drop order. This allows a single
lifetime to be used when a field potentially borrow from two different
fields, by allowing the longer-living field lifetime to be shortened to a
shorter-living field lifetime.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 82 +++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 42 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 6b2c0b1f96b5..6af1f7ae0a08 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -107,6 +107,7 @@ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
     }
 }
 
+#[expect(unused)]
 struct FieldInfo {
     field: Field,
     member: Member,
@@ -124,8 +125,8 @@ struct StructInfo {
     field_idx_map: BTreeMap<Ident, usize>,
     is_tuple_struct: bool,
     self_referential: bool,
-    /// Field lifetime generics.
-    field_lts: Generics,
+    /// Field lifetime generics with outlive chain.
+    field_lts_outlive_chain: Generics,
 }
 
 pub(crate) fn expand_with_cfg(
@@ -346,16 +347,20 @@ fn expand(
 
     // Create a lifetime parameter for each field.
     let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect();
-    let mut field_lts = Generics {
+    let mut field_lts_outlive_chain = Generics {
         lt_token: None,
         params: borrowed_fields
             .iter()
-            .map(|borrowed| {
+            .zip(std::iter::once(None).chain(borrowed_fields.iter().map(Some)))
+            .map(|(borrowed, prev)| {
                 GenericParam::Lifetime(LifetimeParam {
                     attrs: Vec::new(),
                     lifetime: borrowed.lifetime.clone(),
                     colon_token: None,
-                    bounds: Default::default(),
+                    bounds: prev
+                        .iter()
+                        .map(|borrowed| borrowed.lifetime.clone())
+                        .collect(),
                 })
             })
             .collect(),
@@ -363,36 +368,26 @@ fn expand(
         where_clause: None,
     };
 
-    // Insert necessary bounds to make types well-formed.
-    for field in fields.iter() {
-        let Some(borrowed) = &field.borrowed else {
-            continue;
-        };
-        let field_lt = &borrowed.lifetime;
-
-        // For each borrowed field that borrows from other fields, we need to insert outlive bounds.
-        for capture in &field.captures {
-            let lt = &capture.lifetime;
-            field_lts
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(#lt: #field_lt));
-        }
-
-        // For each borrowed field that references a generic, we also need to insert their outlive
-        // bounds so they can refer to generics.
-        for lt in field.generic_lt_captures.iter() {
-            field_lts
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(#lt: #field_lt));
-        }
-
-        for ty in field.generic_ty_captures.iter() {
-            field_lts
-                .make_where_clause()
-                .predicates
-                .push(parse_quote!(#ty: #field_lt));
+    if let Some(last_borrowed_field) = borrowed_fields.last() {
+        let field_lt = &last_borrowed_field.lifetime;
+        for param in struct_.generics.params.iter() {
+            match param {
+                GenericParam::Lifetime(param) => {
+                    let lt = &param.lifetime;
+                    field_lts_outlive_chain
+                        .make_where_clause()
+                        .predicates
+                        .push(parse_quote!(#lt: #field_lt));
+                }
+                GenericParam::Type(param) => {
+                    let ty = &param.ident;
+                    field_lts_outlive_chain
+                        .make_where_clause()
+                        .predicates
+                        .push(parse_quote!(#ty: #field_lt));
+                }
+                GenericParam::Const(_) => (),
+            }
         }
     }
 
@@ -406,7 +401,7 @@ fn expand(
         fields,
         field_idx_map,
         is_tuple_struct,
-        field_lts,
+        field_lts_outlive_chain,
     };
 
     for field in &info.fields {
@@ -703,7 +698,7 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre
     // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types
     // are wellformed, given the bounds that we understand.
 
-    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]);
+    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]);
 
     let (_, ty_generics, _) = generics.split_for_impl();
     let (impl_generics_with_field_lt, _, whr_with_field_lt) =
@@ -847,10 +842,13 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
 
     // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
     // used with `for`.
-    let field_lts = CombinedGenerics(vec![&info.field_lts]);
+    let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]);
     let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
-    let generics_with_this_field_lt =
-        CombinedGenerics(vec![&this_lt_generics, &info.field_lts, generics]);
+    let generics_with_this_field_lt = CombinedGenerics(vec![
+        &this_lt_generics,
+        &info.field_lts_outlive_chain,
+        generics,
+    ]);
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
     let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl();
@@ -1181,8 +1179,8 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
 
     // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
     // used with `for`.
-    let field_lts = CombinedGenerics(vec![&info.field_lts]);
-    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]);
+    let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]);
+    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]);
 
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 13/20] rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (11 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 12/20] rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 14/20] rust: pin-init: internal: pin_data: support mutable borrows Gary Guo
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

`#[borrowed]` attribute explicitly marks a field as potentially being
borrowed by other fields.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 36 ++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 6af1f7ae0a08..c5b664349caf 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -11,8 +11,8 @@
     spanned::Spanned,
     visit::Visit,
     visit_mut::VisitMut,
-    Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam,
-    Member, PathSegment, Token, Type, TypePath,
+    Attribute, Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime,
+    LifetimeParam, Member, Meta, PathSegment, Token, Type, TypePath,
 };
 
 use crate::{
@@ -55,11 +55,25 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
 /// Description of how a field is borrowed.
 #[derive(Clone, Copy, Default, PartialEq, Eq)]
 enum BorrowedKind {
-    /// Implicitly inferreed.
+    /// `#[borrowed]`, or implicitly inferreed.
     #[default]
     Shared,
 }
 
+impl BorrowedKind {
+    fn parse(dcx: &mut DiagCtxt, attrs: &mut Vec<Attribute>) -> Option<Self> {
+        let attr = attrs.extract_single_attr(dcx, "borrowed")?;
+
+        Some(if let Meta::Path(_) = attr.meta {
+            BorrowedKind::Shared
+        } else {
+            // Swallow the error and recover by inferring shared.
+            dcx.error(attr.path(), "unexpected `#[borrowed]` attribute");
+            BorrowedKind::Shared
+        })
+    }
+}
+
 /// Information about a borrowed field.
 struct BorrowedInfo {
     kind: BorrowedKind,
@@ -305,11 +319,25 @@ fn expand(
             })
             .visit_type(&field.ty);
 
+            let borrowed = BorrowedKind::parse(dcx, &mut field.attrs).and_then(|kind| {
+                let lifetime = Lifetime::from_ident(&member.as_ident());
+
+                if bound_lifetimes.contains(&lifetime) {
+                    dcx.error(
+                        &lifetime,
+                        format!("`{lifetime}` appear in generics and would conflict with field lifetime"),
+                    );
+                    return None;
+                }
+
+                Some(BorrowedInfo { kind, lifetime })
+            });
+
             FieldInfo {
                 field,
                 member,
                 pinned,
-                borrowed: None,
+                borrowed,
                 captures,
                 generic_lt_captures,
                 generic_ty_captures,

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 14/20] rust: pin-init: internal: pin_data: support mutable borrows
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (12 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 13/20] rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 15/20] rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation Gary Guo
                   ` (5 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Allow fields to be mutably referenced by other fields in addition to shared
references. In order for this to be sound, the fields that can be mutably
borrowed are blocked from being accessed via field access syntax or
projection to maintain the aliasing requirements.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/examples/selfref.rs      | 13 +++++
 rust/pin-init/internal/src/pin_data.rs | 94 ++++++++++++++++++++++++++++++----
 rust/pin-init/src/__internal.rs        | 66 ++++++++++++++++++------
 3 files changed, 149 insertions(+), 24 deletions(-)

diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs
index b5cdf96b6d1c..5e9494dcc17e 100644
--- a/rust/pin-init/examples/selfref.rs
+++ b/rust/pin-init/examples/selfref.rs
@@ -8,12 +8,18 @@
 struct SelfRef {
     part: &'str str,
     str: String,
+
+    mut_part: &'mut_str mut str,
+    #[borrowed(mut)]
+    mut_str: String,
 }
 
 fn use_self_ref() {
     stack_pin_init!(let foo = pin_init!(SelfRef {
         str: "hello world".to_owned(),
         part: &str[..5],
+        mut_str: "hello world".to_owned(),
+        mut_part: &mut mut_str[..5],
     }));
 
     // Access via projection.
@@ -28,6 +34,13 @@ fn use_self_ref() {
     });
 
     println!("{}", foo.part());
+
+    // Access fields that mutable borrow others are similar to those of shared borrow.
+    println!("{}", foo.as_mut().project().mut_part);
+    println!("{}", foo.mut_part());
+    foo.as_mut().with_project(|proj| {
+        proj.mut_part.make_ascii_uppercase();
+    });
 }
 
 fn main() {
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index c5b664349caf..5f37628fdf0a 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -5,7 +5,7 @@
 use proc_macro2::{Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
 use syn::{
-    parse::{End, Nothing, Parse},
+    parse::{End, Nothing, Parse, ParseStream},
     parse_quote, parse_quote_spanned,
     punctuated::Punctuated,
     spanned::Spanned,
@@ -58,6 +58,8 @@ enum BorrowedKind {
     /// `#[borrowed]`, or implicitly inferreed.
     #[default]
     Shared,
+    // `#[borrowed(mut)]`.
+    Mutable,
 }
 
 impl BorrowedKind {
@@ -67,9 +69,17 @@ fn parse(dcx: &mut DiagCtxt, attrs: &mut Vec<Attribute>) -> Option<Self> {
         Some(if let Meta::Path(_) = attr.meta {
             BorrowedKind::Shared
         } else {
-            // Swallow the error and recover by inferring shared.
-            dcx.error(attr.path(), "unexpected `#[borrowed]` attribute");
-            BorrowedKind::Shared
+            match attr.parse_args_with(|input: ParseStream<'_>| {
+                let _: Token![mut] = input.parse()?;
+                Ok(BorrowedKind::Mutable)
+            }) {
+                Ok(v) => v,
+                Err(err) => {
+                    // Swallow the error and recover by inferring shared.
+                    dcx.error(attr.path(), err);
+                    BorrowedKind::Shared
+                }
+            }
         })
     }
 }
@@ -515,8 +525,17 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream {
 
         let mut ty = ty.to_token_stream();
 
-        // Replace lifetime for self-referential fields.
-        if !field.captures.is_empty() {
+        // Replace lifetime for self-referential fields. For mutable fields, this uses `Erase` to
+        // block direct access.
+        if !field.captures.is_empty()
+            || matches!(
+                field.borrowed,
+                Some(BorrowedInfo {
+                    kind: BorrowedKind::Mutable,
+                    ..
+                })
+            )
+        {
             // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt`
             // implementation and thus may be used inside `Erase`.
             ty = quote!((#ty,));
@@ -921,9 +940,18 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
                 )
             }
 
-            if !f.captures.iter().all(|b| b.variance == Variance::Covariant) {
+            if !f.captures.iter().all(|b| b.variance == Variance::Covariant)
+                || matches!(
+                    f.borrowed,
+                    Some(BorrowedInfo {
+                        kind: BorrowedKind::Mutable,
+                        ..
+                    })
+                )
+            {
                 // If the type is not covariant, it must omitted, as projection shortens the
                 // lifetime to `'__this`.
+                // Mutable borrow must be omitted for aliasing reason.
                 (
                     quote!(
                         #vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>,
@@ -991,7 +1019,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
                 this_lt.clone()
             };
 
-            if f.pinned {
+            if matches!(
+                f.borrowed,
+                Some(BorrowedInfo {
+                    kind: BorrowedKind::Mutable,
+                    ..
+                })
+            ) {
+                // If the type is not covariant, it must omitted, as projection shortens the
+                // lifetime to `'__this`.
+                // Mutable borrow must be omitted for aliasing reason.
+                (
+                    quote!(
+                        #vis #name ::pin_init::__internal::NotVisible<&#lt #mut_token #ty>,
+                    ),
+                    quote!(
+                        #name ::pin_init::__internal::NotVisible::new(),
+                    ),
+                )
+            } else if f.pinned {
                 (
                     quote!(
                         #vis #name ::core::pin::Pin<&#lt #mut_token #ty>,
@@ -1111,6 +1157,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
             continue;
         }
 
+        if matches!(
+            f.borrowed,
+            Some(BorrowedInfo {
+                kind: BorrowedKind::Mutable,
+                ..
+            })
+        ) {
+            // Mutably borrowed fields cannot be accessed directly under any circumstance.
+            continue;
+        }
+
         if f.captures.iter().all(|b| b.variance == Variance::Covariant) {
             let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`.");
             let vis = &f.field.vis;
@@ -1266,7 +1323,26 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
                     // assumptions on the lifetime except for those implied by the struct's bounds,
                     // and we have validated them in `generate_drop_check`.
                     quote!(SelfRefSlot),
-                    quote!(#lifetime,),
+                    quote!(#lifetime, ::pin_init::__internal::Shared, ),
+                ),
+                Some(BorrowedInfo {
+                    kind: BorrowedKind::Mutable,
+                    lifetime,
+                }) => (
+                    // For borrowed fields, create a `SelfRefSlot`, which after initialization
+                    // turns into a `SelfRefDropGuard` instead of `DropGuard`.
+                    //
+                    // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T`
+                    // instead of `&'guard T` for let bindings; this allows it to be used to be
+                    // used to initialize other fields.
+                    //
+                    // The soundness of doing so relies on fact that `__make_init` requires a
+                    // higher-ranked trait bound on the closure. Within the closure (which is the
+                    // caller of the generated slot projection functions here), it can make no
+                    // assumptions on the lifetime except for those implied by the struct's bounds,
+                    // and we have validated them in `generate_drop_check`.
+                    quote!(SelfRefSlot),
+                    quote!(#lifetime, ::pin_init::__internal::Mutable, ),
                 ),
             };
 
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index ddd99e705c93..56ea6d927c9e 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -361,6 +361,9 @@ fn drop(&mut self) {
     }
 }
 
+pub struct Shared;
+pub struct Mutable;
+
 /// Represent an uninitialized field in a pinned struct that will be referenced by other fields.
 ///
 /// # Invariants
@@ -368,12 +371,12 @@ fn drop(&mut self) {
 /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory
 ///   and will live longer than `'a`.
 /// - If `P` is `Pinned`, then `ptr` is structurally pinned.
-pub struct SelfRefSlot<'a, P, T: ?Sized> {
-    pub ptr: *mut T,
-    pub _phantom: PhantomData<(P, &'a mut T)>,
+pub struct SelfRefSlot<'a, M, P, T: ?Sized> {
+    ptr: *mut T,
+    _phantom: PhantomData<(M, P, &'a mut T)>,
 }
 
-impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> {
+impl<'a, M, P, T: ?Sized> SelfRefSlot<'a, M, P, T> {
     /// # Safety
     ///
     /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed
@@ -390,7 +393,7 @@ pub unsafe fn new(ptr: *mut T) -> Self {
 
     /// Initialize the field by value.
     #[inline]
-    pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T>
+    pub fn write(self, value: T) -> SelfRefDropGuard<'a, M, P, T>
     where
         T: Sized,
     {
@@ -404,10 +407,10 @@ pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T>
     }
 }
 
-impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> {
+impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Unpinned, T> {
     /// Initialize the field.
     #[inline]
-    pub fn init<E>(self, init: impl Init<T, E>) -> Result<SelfRefDropGuard<'a, Unpinned, T>, E> {
+    pub fn init<E>(self, init: impl Init<T, E>) -> Result<SelfRefDropGuard<'a, M, Unpinned, T>, E> {
         // SAFETY:
         // - `self.ptr` is valid and properly aligned.
         // - when `Err` is returned, we also propagate the error without touching `slot`;
@@ -421,10 +424,13 @@ pub fn init<E>(self, init: impl Init<T, E>) -> Result<SelfRefDropGuard<'a, Unpin
     }
 }
 
-impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> {
+impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Pinned, T> {
     /// Initialize the field.
     #[inline]
-    pub fn init<E>(self, init: impl PinInit<T, E>) -> Result<SelfRefDropGuard<'a, Pinned, T>, E> {
+    pub fn init<E>(
+        self,
+        init: impl PinInit<T, E>,
+    ) -> Result<SelfRefDropGuard<'a, M, Pinned, T>, E> {
         // SAFETY:
         // - `ptr` is valid
         // - when `Err` is returned, we also propagate the error without touching `ptr`;
@@ -447,12 +453,12 @@ pub fn init<E>(self, init: impl PinInit<T, E>) -> Result<SelfRefDropGuard<'a, Pi
 /// - `ptr` is valid, properly aligned and live longer than `'a`.
 /// - `*ptr` is initialized and owned by this guard.
 /// - if `P` is `Pinned`, `ptr` is pinned.
-pub struct SelfRefDropGuard<'a, P, T: ?Sized> {
+pub struct SelfRefDropGuard<'a, M, P, T: ?Sized> {
     ptr: *mut T,
-    phantom: PhantomData<(P, &'a mut T)>,
+    phantom: PhantomData<(M, P, &'a mut T)>,
 }
 
-impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> {
+impl<'a, M, P, T: ?Sized> SelfRefDropGuard<'a, M, P, T> {
     /// Creates a drop guard and transfer the ownership of the pointer content.
     ///
     /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`].
@@ -472,7 +478,7 @@ pub unsafe fn new(ptr: *mut T) -> Self {
     }
 }
 
-impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> {
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Unpinned, T> {
     /// Create a let binding for accessor use.
     #[inline]
     pub fn let_binding(&mut self) -> &'a T {
@@ -487,7 +493,7 @@ pub fn let_binding_in_dropck(&mut self) -> &T {
     }
 }
 
-impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> {
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Pinned, T> {
     /// Create a let binding for accessor use.
     #[inline]
     pub fn let_binding(&mut self) -> Pin<&'a T> {
@@ -503,7 +509,37 @@ pub fn let_binding_in_dropck(&mut self) -> Pin<&T> {
     }
 }
 
-impl<P, T: ?Sized> Drop for SelfRefDropGuard<'_, P, T> {
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Unpinned, T> {
+    /// Create a let binding for accessor use.
+    #[inline]
+    pub fn let_binding(&mut self) -> &'a mut T {
+        // SAFETY: Per type invariant.
+        unsafe { &mut *self.ptr }
+    }
+
+    /// Create a let binding for accessor use in dropck.
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> &mut T {
+        self.let_binding()
+    }
+}
+
+impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Pinned, T> {
+    /// Create a let binding for accessor use.
+    #[inline]
+    pub fn let_binding(&mut self) -> Pin<&'a mut T> {
+        // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized,
+        // exclusively accessible and pinned per type invariant.
+        unsafe { Pin::new_unchecked(&mut *self.ptr) }
+    }
+
+    #[inline]
+    pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> {
+        self.let_binding()
+    }
+}
+
+impl<M, P, T: ?Sized> Drop for SelfRefDropGuard<'_, M, P, T> {
     #[inline]
     fn drop(&mut self) {
         // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard.

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 15/20] rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (13 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 14/20] rust: pin-init: internal: pin_data: support mutable borrows Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 16/20] rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance Gary Guo
                   ` (4 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Add support for explicit self-referential annotations.

`#[uses]` attribute is used to mark what other field lifetimes are
captured by this field, and also the variance of the type in respect to the
field lifetimes. For example,

    #[uses('a: covariant, 'b: invariant)]

indicates that the field captures the field lifetime `'a` covariantly, and
field lifetime `'b` invariantly. Many types are covariant, so this is the
default variance if the variance is omitted (e.g. `#[uses('a)]`),
consistent with the automatically inferred borrow.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 99 ++++++++++++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 4 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 5f37628fdf0a..5f9f4f3ac39a 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -21,6 +21,9 @@
 };
 
 pub(crate) mod kw {
+    syn::custom_keyword!(covariant);
+    syn::custom_keyword!(invariant);
+    syn::custom_keyword!(contravariant);
     syn::custom_keyword!(PinnedDrop);
 }
 
@@ -93,9 +96,37 @@ struct BorrowedInfo {
 
 #[derive(Clone, Copy, Default, PartialEq, Eq)]
 enum Variance {
-    /// Implicitly inferred variance.
+    /// `covariant` annotation, or implicitly inferred.
     #[default]
     Covariant,
+    // `invariant` annotation.
+    Invariant,
+}
+
+impl Parse for Variance {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let lh = input.lookahead1();
+        Ok(if lh.peek(kw::covariant) {
+            let _: kw::covariant = input.parse()?;
+            Variance::Covariant
+        } else if lh.peek(kw::invariant) {
+            let _: kw::invariant = input.parse()?;
+            Variance::Invariant
+        } else if lh.peek(kw::contravariant) {
+            // Field lifetimes are inherently covariant, so combining with contravariance,
+            // we would constrain it to be invariant.
+            let token: kw::contravariant = input.parse()?;
+            DiagCtxt::current(|dcx| {
+                dcx.error(
+                    token,
+                    "field lifetimes cannot be `contravariant`; use `invariant` instead",
+                )
+            });
+            Variance::Invariant
+        } else {
+            Err(lh.error())?
+        })
+    }
 }
 
 /// Information about field lifetimes captured in a type.
@@ -131,7 +162,65 @@ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
     }
 }
 
-#[expect(unused)]
+impl Parse for Capture {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let lifetime = input.parse()?;
+        let variance = if input.peek(Token![:]) {
+            let _: Token![:] = input.parse()?;
+            input.parse()?
+        } else {
+            // If variance is not explicitly specified, infer covariance by default.
+            Variance::Covariant
+        };
+        Ok(Capture { variance, lifetime })
+    }
+}
+
+impl Capture {
+    fn parse_list(
+        dcx: &mut DiagCtxt,
+        attrs: &mut Vec<Attribute>,
+        bound_lifetimes: &BTreeSet<&Lifetime>,
+        field_idx_map: &BTreeMap<Ident, usize>,
+    ) -> Option<(BTreeSet<Capture>, Variance)> {
+        let attr = attrs.extract_single_attr(dcx, "uses")?;
+        let punctuated: Punctuated<Capture, Token![,]> = attr
+            .parse_args_with(Punctuated::parse_terminated)
+            .map_err(ErrorGuaranteed::from)
+            .ok()?;
+
+        // Check for misuses inside attribute.
+        let mut set = BTreeSet::new();
+        for borrow in punctuated {
+            let lt = &borrow.lifetime;
+            if set.contains(&borrow) {
+                dcx.error(lt, format!("lifetime `{lt}` is mentioned more than once"));
+                continue;
+            }
+
+            if bound_lifetimes.contains(lt) {
+                dcx.error(
+                    lt,
+                    format!("`{lt}` is a struct generics and cannot be used in `#[uses]`"),
+                );
+                continue;
+            }
+
+            if lt.ident != "_" && !field_idx_map.contains_key(&lt.ident) {
+                dcx.error(lt, format!("`{lt}` is not a field name"));
+                continue;
+            }
+
+            set.insert(borrow);
+        }
+
+        let wildcard = Lifetime::new("'_", Span::mixed_site());
+        let wildcard_variance = set.take(&wildcard).map(|b| b.variance).unwrap_or_default();
+        Some((set, wildcard_variance))
+    }
+}
+
+#[allow(unused)]
 struct FieldInfo {
     field: Field,
     member: Member,
@@ -275,8 +364,10 @@ fn expand(
                 }),
             };
 
-            let mut captures = BTreeSet::new();
-            let wildcard_variance = Variance::default();
+            // Parse `#[uses]` attribute.
+            let (mut captures, wildcard_variance) =
+                Capture::parse_list(dcx, &mut field.attrs, &bound_lifetimes, &field_idx_map)
+                    .unwrap_or_default();
 
             let mut generic_lt_captures = BTreeSet::new();
             let mut generic_ty_captures = BTreeSet::new();

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 16/20] rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (14 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 15/20] rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 17/20] rust: pin-init: internal: pin_data: complete invariant borrow support Gary Guo
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

If invariant field captures a field lifetime, then we also need to make
sure that field is also invariant. Imagine this struct:

    #[pin_data]
    struct SelfRef<'a> {
        #[uses('outer: invariant)]
        part: Mutex<&'outer str>,
        outer: &'a String,
    }

    fn new<'a>(str: &'a String) -> impl PinInit<SelfRef<'a>, Infallible> {
        pin_init!(SelfRef {
            outer: str,
            part: Mutex::new(*outer),
        })
    }

If we make this struct covariant over `'a`, then we can have the following
case:

    let mut long = "hello world".to_owned();
    let s = Box::pin_init(new(&long)).unwrap();
    {
        let mut short = "hello world".to_owned();
        // If `s` is covariant, this would be okay, because we shorten from
        // `SelfRef<'long>` to `SelfRef<'short>`.
        s.with_project_ref(|p| {
            *p.part.lock().unwrap() = &short;
        });
    }

Conceptually, a field's type must outlive the field's lifetime, so if we
have a covariant `'a`, we can have a shortened `SelfRef<'short_a>` where
`'outer` outlives `'short_a`. But the wellformedness requirement of the
field will imply `'short_a: 'outer`, which enables the `&'short_a` to
`'outer` coercion, effectively making the field lifetime `'f` behave
covariantly, too, breaking the requirement that it is invariant.

Therefore, compute an invariant closure and use an additional generics on
`Borrowed` to allow capturing things invariantly. Note that we do capture
all parameters explicitly rather than capture the field type invariantly,
because if type aliases are involved, we might be syntactically determining
that the field uses a type parameter but actually not, causing the
invariance enforcement to be missed.

Outlive bounds between field lifetimes can also cause the same issue (an
invariant field lifetime cannot be a lower bound of a covariant field
lifetime). Since we cannot deduce whether any implied bounds from type
wellformness would create such outlive relationships, prevent such bounds
from happening by using a split outlive chain.

Note that this is not a soundness hole in itself in absence of
`with_project_ref`, because with single field accessors only, the field
lifetimes of the fields are not connected; in methods like `with_project`
lifetimes are invariant so shortening cannot happen. However, such method
is likely desirable, so include the variance rule before it has been
heavily relied upon.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 175 ++++++++++++++++++++++++++++++++-
 rust/pin-init/src/__internal.rs        |   8 +-
 2 files changed, 174 insertions(+), 9 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 5f9f4f3ac39a..ffebba98526d 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -92,6 +92,8 @@ struct BorrowedInfo {
     kind: BorrowedKind,
     /// Field lifetime for this field.
     lifetime: Lifetime,
+    // Variance of the field lifetime, as captured by other fields.
+    lt_variance: Variance,
 }
 
 #[derive(Clone, Copy, Default, PartialEq, Eq)]
@@ -238,8 +240,11 @@ struct StructInfo {
     field_idx_map: BTreeMap<Ident, usize>,
     is_tuple_struct: bool,
     self_referential: bool,
-    /// Field lifetime generics with outlive chain.
+    /// Field lifetime genercis with outlive chain.
     field_lts_outlive_chain: Generics,
+    /// Field lifetime genercis with outlive chain, with one chain for covariant fields and one
+    /// chain for invariant fields.
+    field_lts_split_variance_outlive_chain: Generics,
 }
 
 pub(crate) fn expand_with_cfg(
@@ -431,7 +436,7 @@ fn expand(
                     return None;
                 }
 
-                Some(BorrowedInfo { kind, lifetime })
+                Some(BorrowedInfo { kind, lifetime, lt_variance: Variance::default() })
             });
 
             FieldInfo {
@@ -455,6 +460,7 @@ fn expand(
                 kind: BorrowedKind::Shared,
                 // Obtaining from `field` instead of `field_name` for the correct span.
                 lifetime: Lifetime::from_ident(&field.member.as_ident()),
+                lt_variance: Variance::Covariant,
             });
         }
     }
@@ -474,8 +480,63 @@ fn expand(
     })
     .visit_generics(&struct_.generics);
 
+    // Obtain an closure of invariant fields.
+    //
+    // If a field lifetime is used invariantly, we also need to make sure that
+    // for each generic parameter `T: 'field` bound, `T` is also captured
+    // invariantly (same is true for lifetime parameters). For example, say we
+    // have a struct `SelfRef<'a>` and a field `f: &'a ()`. For wellformedness,
+    // we would have `'a: 'f`. If we have a covariant `'a`, we can observe a
+    // shortened `SelfRef<'short_a>` where `'f` outlives `'short_a`, conflicting
+    // with the wellformedness bound `'short_a: 'f`. This will enable the
+    // `&'short_a ()` to `&'f ()` coercion, which effectively shortens `'f` too,
+    // so we shortened the field lifetime despite it being invariant.
+    let mut invariant_field_idx = BTreeSet::new();
+    let mut worklist = Vec::new();
+    for field in fields.iter() {
+        for borrow in field.captures.iter() {
+            if let Variance::Invariant = borrow.variance {
+                let Some(borrow_idx) = fields
+                    .iter()
+                    .position(|f| f.member.as_ident() == borrow.lifetime.ident)
+                else {
+                    continue;
+                };
+                if invariant_field_idx.insert(borrow_idx) {
+                    worklist.push(&fields[borrow_idx]);
+                }
+            }
+        }
+    }
+    while let Some(field) = worklist.pop() {
+        for borrow in field.captures.iter() {
+            let Some(borrow_idx) = fields
+                .iter()
+                .position(|f| f.member.as_ident() == borrow.lifetime.ident)
+            else {
+                continue;
+            };
+            let borrow = &fields[borrow_idx];
+            if invariant_field_idx.insert(borrow_idx) {
+                worklist.push(borrow);
+            }
+        }
+    }
+    for idx in invariant_field_idx {
+        fields[idx].borrowed.as_mut().unwrap().lt_variance = Variance::Invariant;
+    }
+
     // Create a lifetime parameter for each field.
     let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect();
+    let borrowed_covariant_fields: Vec<_> = borrowed_fields
+        .iter()
+        .filter(|f| f.lt_variance == Variance::Covariant)
+        .collect();
+    let borrowed_invariant_fields: Vec<_> = borrowed_fields
+        .iter()
+        .filter(|f| f.lt_variance == Variance::Invariant)
+        .collect();
+
     let mut field_lts_outlive_chain = Generics {
         lt_token: None,
         params: borrowed_fields
@@ -520,6 +581,96 @@ fn expand(
         }
     }
 
+    let mut field_lts_split_variance_outlive_chain = Generics {
+        lt_token: Some(Default::default()),
+        params: borrowed_covariant_fields
+            .iter()
+            .zip(std::iter::once(None).chain(borrowed_covariant_fields.iter().map(Some)))
+            .map(|(borrowed, prev)| {
+                GenericParam::Lifetime(LifetimeParam {
+                    attrs: Vec::new(),
+                    lifetime: borrowed.lifetime.clone(),
+                    colon_token: None,
+                    bounds: prev
+                        .iter()
+                        .map(|borrowed| borrowed.lifetime.clone())
+                        .collect(),
+                })
+            })
+            .chain(
+                borrowed_invariant_fields
+                    .iter()
+                    .zip(std::iter::once(None).chain(borrowed_invariant_fields.iter().map(Some)))
+                    .map(|(borrowed, prev)| {
+                        GenericParam::Lifetime(LifetimeParam {
+                            attrs: Vec::new(),
+                            lifetime: borrowed.lifetime.clone(),
+                            colon_token: None,
+                            bounds: prev
+                                .iter()
+                                .map(|borrowed| borrowed.lifetime.clone())
+                                .collect(),
+                        })
+                    }),
+            )
+            .collect(),
+        gt_token: Some(Default::default()),
+        where_clause: None,
+    };
+
+    // For `field_lts_split_variance_outlive_chain`, we may need to insert some additional bounds
+    // for types to be WF.
+    for field in fields.iter() {
+        let Some(borrowed) = &field.borrowed else {
+            continue;
+        };
+        let field_lt = &borrowed.lifetime;
+
+        // For each borrowed field that references a generic, we also need to insert their outlive
+        // bounds so they can refer to generics.
+        for lt in field.generic_lt_captures.iter() {
+            field_lts_split_variance_outlive_chain
+                .make_where_clause()
+                .predicates
+                .push(parse_quote!(#lt: #field_lt));
+        }
+
+        for ty in field.generic_ty_captures.iter() {
+            field_lts_split_variance_outlive_chain
+                .make_where_clause()
+                .predicates
+                .push(parse_quote!(#ty: #field_lt));
+        }
+
+        // If a field is invariant, then the invariance closure rule will make all borrowed fields
+        // to be invariant, so they're already captured in `field_lts_split_variance_outlive_chain`.
+        if borrowed.lt_variance != Variance::Covariant {
+            continue;
+        }
+
+        for capture in field.captures.iter() {
+            let Some(&idx) = field_idx_map.get(&capture.lifetime.ident) else {
+                continue;
+            };
+
+            let prev_borrowed = fields[idx].borrowed.as_ref().unwrap();
+
+            // If borrowed field is covariant, it's already captured in
+            // `field_lts_split_variance_outlive_chain`.
+            if prev_borrowed.lt_variance == Variance::Invariant {
+                // Covariant field borrowing an invariant field. This is not captured in the chain
+                // so we need to add additional bound. This bound is okay, as the invariant lifetime
+                // is the longer living one, so arbitrary shortening of the covariant one does not
+                // violate their relation.
+                let param = field_lts_split_variance_outlive_chain
+                    .lifetimes_mut()
+                    .find(|l| l.lifetime == prev_borrowed.lifetime)
+                    .unwrap();
+                param.bounds.push(borrowed.lifetime.clone());
+            }
+        }
+    }
+
     struct_.fields = Fields::Unit;
     let info = StructInfo {
         self_referential: fields
@@ -531,6 +682,7 @@ fn expand(
         field_idx_map,
         is_tuple_struct,
         field_lts_outlive_chain,
+        field_lts_split_variance_outlive_chain,
     };
 
     for field in &info.fields {
@@ -639,8 +791,18 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream {
             ty = quote!(::pin_init::__internal::Erase<#ty>);
         };
 
-        if field.borrowed.is_some() {
-            ty = quote!(::pin_init::__internal::Borrowed<#ty>);
+        if let Some(borrowed) = &field.borrowed {
+            let mut invariance_capture = Vec::new();
+            if borrowed.lt_variance == Variance::Invariant {
+                for lt in &field.generic_lt_captures {
+                    invariance_capture.push(quote!(&#lt ()));
+                }
+
+                for ty in &field.generic_ty_captures {
+                    invariance_capture.push(quote!(::core::marker::PhantomData<#ty>));
+                }
+            }
+            ty = quote!(::pin_init::__internal::Borrowed<#ty, (#(#invariance_capture,)*)>);
         }
 
         quote! {
@@ -836,7 +998,8 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre
     // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types
     // are wellformed, given the bounds that we understand.
 
-    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]);
+    let generics_with_field_lt =
+        CombinedGenerics(vec![&info.field_lts_split_variance_outlive_chain, generics]);
 
     let (_, ty_generics, _) = generics.split_for_impl();
     let (impl_generics_with_field_lt, _, whr_with_field_lt) =
@@ -1400,6 +1563,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
                 Some(BorrowedInfo {
                     kind: BorrowedKind::Shared,
                     lifetime,
+                    ..
                 }) => (
                     // For borrowed fields, create a `SelfRefSlot`, which after initialization
                     // turns into a `SelfRefDropGuard` instead of `DropGuard`.
@@ -1419,6 +1583,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
                 Some(BorrowedInfo {
                     kind: BorrowedKind::Mutable,
                     lifetime,
+                    ..
                 }) => (
                     // For borrowed fields, create a `SelfRefSlot`, which after initialization
                     // turns into a `SelfRefDropGuard` instead of `DropGuard`.
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index 56ea6d927c9e..516dd893746e 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -676,7 +676,7 @@ unsafe impl<F: EraseLt> Sync for Erase<F>
 /// This should be switched to `UnsafePinned` when it is stable.
 /// NOTE: This type needs to be covariant; Rust's 1.89+'s `UnsafePinned` is invariant.
 #[repr(transparent)]
-pub struct Borrowed<T: ?Sized>(PhantomPinned, T);
+pub struct Borrowed<T: ?Sized, P>(PhantomInvariant<P>, PhantomPinned, T);
 
 // Lifetimes not needed by drop glue are considered by Rust's drop check to be considered
 // `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of
@@ -702,17 +702,17 @@ unsafe impl<F: EraseLt> Sync for Erase<F>
 // outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a
 // dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict
 // outlive relation is needed.
-impl<T: ?Sized> Drop for Borrowed<T> {
+impl<T: ?Sized, P> Drop for Borrowed<T, P> {
     #[inline(always)]
     fn drop(&mut self) {}
 }
 
-impl<T: ?Sized> Deref for Borrowed<T> {
+impl<T: ?Sized, P> Deref for Borrowed<T, P> {
     type Target = T;
 
     #[inline(always)]
     fn deref(&self) -> &T {
-        &self.1
+        &self.2
     }
 }
 

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 17/20] rust: pin-init: internal: pin_data: complete invariant borrow support
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (15 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 16/20] rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 18/20] rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible Gary Guo
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Non-covariant types can already be accessed inside projections. As
projections are only generated for `Pin<&mut T>`, they're not accessible
otherwise. Add `with_{field_name}` methods so fields can be accessed using
closures with just `&T`.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/examples/selfref.rs      | 12 ++++++++++++
 rust/pin-init/internal/src/pin_data.rs | 19 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs
index 5e9494dcc17e..5212b9255b75 100644
--- a/rust/pin-init/examples/selfref.rs
+++ b/rust/pin-init/examples/selfref.rs
@@ -6,6 +6,9 @@
 
 #[pin_data]
 struct SelfRef {
+    #[uses('_: invariant)]
+    not_cov: Box<dyn Fn(&'str str) -> bool + 'str>,
+
     part: &'str str,
     str: String,
 
@@ -20,6 +23,7 @@ fn use_self_ref() {
         part: &str[..5],
         mut_str: "hello world".to_owned(),
         mut_part: &mut mut_str[..5],
+        not_cov: Box::new(move |s| s == str),
     }));
 
     // Access via projection.
@@ -41,6 +45,14 @@ fn use_self_ref() {
     foo.as_mut().with_project(|proj| {
         proj.mut_part.make_ascii_uppercase();
     });
+
+    // Access non-covariant type using `with_` accessor.
+    foo.with_not_cov(|not_cov| {
+        not_cov("");
+    });
+
+    // Access non-covariant type using `with_project`.
+    foo.as_mut().with_project(|proj| (proj.not_cov)(proj.str));
 }
 
 fn main() {
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index ffebba98526d..3abd06ca92dc 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -1446,7 +1446,24 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
                 }
             ))
         } else {
-            continue;
+            let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`.");
+            let vis = &f.field.vis;
+            let with_ident = format_ident!("with_{ident}");
+
+            let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect();
+            let ty = &f.field.ty;
+
+            accessors.push(quote!(
+                #[doc = #f_doc]
+                #[inline]
+                #vis fn #with_ident<'__this, R>(
+                    &'__this self,
+                    f: impl for<#(#all_lifetimes,)*> ::core::ops::FnOnce(&'__this #ty) -> R,
+                ) -> R {
+                    // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`.
+                    f(unsafe { ::core::mem::transmute(&self.#member) })
+                }
+            ))
         }
     }
 

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 18/20] rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (16 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 17/20] rust: pin-init: internal: pin_data: complete invariant borrow support Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 19/20] rust: pin-init: internal: pin_data: support shared projection Gary Guo
  2026-10-08 19:24 ` [PATCH v2 20/20] rust: pin-init: internal: pin_data: support existential lifetimes Gary Guo
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

Currently lifetimes are replaced via function type and `FnOutput` trait.
This is very general approach as it uses generic associated type to replace
lifetime, so it can even work when macros are involved. This does cause
more generated code, and does not render in documentation nicely.

Thus, just replace the lifetime in the AST if no macros are involved.

Acked-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/util.rs | 112 +++++++++++++++++++++++++++++++++----
 1 file changed, 102 insertions(+), 10 deletions(-)

diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs
index 59054f5934fb..4d3331b852b4 100644
--- a/rust/pin-init/internal/src/util.rs
+++ b/rust/pin-init/internal/src/util.rs
@@ -1,12 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
-use std::collections::BTreeSet;
+use std::collections::{BTreeMap, BTreeSet};
 
 use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, ToTokens};
 use syn::{
-    parse_quote, visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime,
-    Member, Token, Type, TypePath,
+    parse_quote, visit::Visit, visit_mut::VisitMut, Attribute, BoundLifetimes, GenericParam,
+    Generics, Index, Lifetime, Member, Token, Type, TypePath,
 };
 
 use crate::DiagCtxt;
@@ -388,21 +388,113 @@ fn visit_type_path(&mut self, ty: &'a TypePath) {
 }
 
 pub(crate) trait TypeExt {
+    /// Check if the type includes macro invocations.
+    ///
+    /// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes
+    /// special handling is required.
+    fn has_macro(&self) -> bool;
+
     fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type;
 }
 
 impl TypeExt for Type {
+    fn has_macro(&self) -> bool {
+        struct HasMacro(bool);
+
+        impl<'ast> Visit<'ast> for HasMacro {
+            fn visit_macro(&mut self, _: &'ast syn::Macro) {
+                self.0 = true;
+            }
+        }
+
+        let mut visitor = HasMacro(false);
+        visitor.visit_type(self);
+        visitor.0
+    }
+
     fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type {
         if needle.is_empty() {
             return self.clone();
         }
 
-        parse_quote!(
-            <
-                for<#(#needle,)*> fn(#(&#needle (),)*) -> #self
-                    as
-                ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)>
-            >::Output
-        )
+        // If the type has macro, we cannot peek into it. Use some different approach to replace
+        // the type using GAT.
+        if self.has_macro() {
+            return parse_quote!(
+                <
+                    for<#(#needle,)*> fn(#(&#needle (),)*) -> #self
+                        as
+                    ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)>
+                >::Output
+            );
+        }
+
+        struct LifetimeReplacer<'a> {
+            to_replace: BTreeMap<&'a Lifetime, &'a Lifetime>,
+        }
+
+        impl<'a> LifetimeReplacer<'a> {
+            fn with_bound_lifetimes(
+                &mut self,
+                bound: Option<&BoundLifetimes>,
+                f: impl FnOnce(&mut Self),
+            ) {
+                // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`,
+                // temporarily remove them from to_replace if they're.
+
+                let mut removed = Vec::new();
+                if let Some(bound) = bound {
+                    for lt in &bound.lifetimes {
+                        let GenericParam::Lifetime(lt) = lt else {
+                            continue;
+                        };
+                        if let Some(entry) = self.to_replace.remove_entry(&lt.lifetime) {
+                            removed.push(entry);
+                        }
+                    }
+                }
+
+                f(self);
+
+                for (key, val) in removed {
+                    self.to_replace.insert(key, val);
+                }
+            }
+        }
+
+        impl VisitMut for LifetimeReplacer<'_> {
+            fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) {
+                if let Some(&replacement) = self.to_replace.get(lt) {
+                    *lt = replacement.clone();
+                }
+            }
+
+            fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) {
+                self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| {
+                    this.visit_path_mut(&mut bound.path)
+                });
+            }
+
+            fn visit_type_bare_fn_mut(&mut self, bare_fn: &mut syn::TypeBareFn) {
+                self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| {
+                    for input in bare_fn.inputs.iter_mut() {
+                        this.visit_bare_fn_arg_mut(input);
+                    }
+
+                    this.visit_return_type_mut(&mut bare_fn.output);
+                });
+            }
+        }
+
+        let mut ret = self.clone();
+        LifetimeReplacer {
+            to_replace: needle
+                .iter()
+                .copied()
+                .zip(replacement.iter().copied())
+                .collect(),
+        }
+        .visit_type_mut(&mut ret);
+        ret
     }
 }

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 19/20] rust: pin-init: internal: pin_data: support shared projection
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (17 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 18/20] rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  2026-10-08 19:24 ` [PATCH v2 20/20] rust: pin-init: internal: pin_data: support existential lifetimes Gary Guo
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

For fields that self-references, it is desirable that projection can
happen on shared references too, so lifetime between different fields can
be correlated. Add support for that with `with_project_ref`.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 134 +++++++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 3abd06ca92dc..3d7726e12b3b 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -1150,10 +1150,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         &info.field_lts_outlive_chain,
         generics,
     ]);
+    let generics_with_this_field_ref_lt = CombinedGenerics(vec![
+        &this_lt_generics,
+        &info.field_lts_split_variance_outlive_chain,
+        generics,
+    ]);
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
     let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl();
     let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl();
+    let (_, ty_generics_with_this_field_ref_lt, _) =
+        generics_with_this_field_ref_lt.split_for_impl();
 
     let this = format_ident!("this");
 
@@ -1467,6 +1474,109 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         }
     }
 
+    let (fields_ref_decl_lt, fields_ref_proj_lt): (Vec<_>, Vec<_>) = info
+        .fields
+        .iter()
+        .map(|f| {
+            let vis = &f.field.vis;
+            let ident = f.member.as_ident();
+            let member = &f.member;
+            let name = (!info.is_tuple_struct).then(|| quote!(#ident:));
+
+            let ty = &f.field.ty;
+
+            let mut accessor = quote!(&#this.#member);
+            if !f.captures.is_empty() || f.borrowed.is_some() {
+                accessor = quote!(
+                    // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`.
+                    // We cannot include explicit type name here as the field lifetimes are nameable
+                    // in this context.
+                    unsafe { ::core::mem::transmute(#accessor) }
+                )
+            }
+
+            // In `with_project`, borrowed fields have their field lifetime available, so use it
+            // instead of `'__this`.
+            let lt = if f.borrowed.is_some() {
+                Lifetime::from_ident(&ident)
+            } else {
+                this_lt.clone()
+            };
+
+            if matches!(
+                f.borrowed,
+                Some(BorrowedInfo {
+                    kind: BorrowedKind::Mutable,
+                    ..
+                })
+            ) {
+                // Mutable borrow must be omitted for aliasing reason.
+                (
+                    quote!(
+                        #vis #name ::pin_init::__internal::NotVisible<&#lt #ty>,
+                    ),
+                    quote!(
+                        #name ::pin_init::__internal::NotVisible::new(),
+                    ),
+                )
+            } else if f.pinned {
+                (
+                    quote!(
+                        #vis #name ::core::pin::Pin<&#lt #ty>,
+                    ),
+                    quote!(
+                        // SAFETY: this field is structurally pinned.
+                        #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) },
+                    ),
+                )
+            } else {
+                (
+                    quote!(
+                        #vis #name &#lt #ty,
+                    ),
+                    quote!(
+                        #name #accessor,
+                    ),
+                )
+            }
+        })
+        .collect();
+
+    let projection_ref_lt = format_ident!("__ProjectionRef");
+    let (projection_ref_lt_def, projection_ref_lt_init) = if info.is_tuple_struct {
+        (
+            quote!(
+                #vis struct #projection_ref_lt #generics_with_this_field_ref_lt(
+                    #(#fields_ref_decl_lt)*
+                    ::core::marker::PhantomData<&'__this #ident #ty_generics>,
+                ) #whr;
+            ),
+            quote!(
+                #projection_ref_lt(
+                    #(#fields_ref_proj_lt)*
+                    ::core::marker::PhantomData,
+                )
+            ),
+        )
+    } else {
+        (
+            quote! {
+                #vis struct #projection_ref_lt #generics_with_this_field_ref_lt
+                    #whr
+                {
+                    #(#fields_ref_decl_lt)*
+                    ___pin_phantom_data: ::core::marker::PhantomData<&'__this #ident #ty_generics>,
+                }
+            },
+            quote! {
+                #projection_ref_lt {
+                    #(#fields_ref_proj_lt)*
+                    ___pin_phantom_data: ::core::marker::PhantomData,
+                }
+            },
+        )
+    };
+
     quote! {
         #[doc = #docs]
         // Allow `non_snake_case` since the same warning will be emitted on
@@ -1482,6 +1592,12 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
         #[doc(hidden)]
         #projection_lt_def
 
+        // Allow `non_snake_case` since the same warning will be emitted on
+        // the struct definition.
+        #[allow(dead_code, non_snake_case)]
+        #[doc(hidden)]
+        #projection_ref_lt_def
+
         impl #impl_generics #ident #ty_generics
             #whr
         {
@@ -1520,6 +1636,24 @@ impl #impl_generics #ident #ty_generics
                 f(#projection_lt_init)
             }
 
+            /// Pin-projects all fields of `Self` from a shared reference with proper lifetime.
+            ///
+            /// These fields are structurally pinned:
+            #(#[doc = #structurally_pinned_fields_docs])*
+            ///
+            /// These fields are **not** structurally pinned:
+            #(#[doc = #not_structurally_pinned_fields_docs])*
+            #[inline]
+            #vis fn with_project_ref<'__this, R>(
+                self: ::core::pin::Pin<&'__this Self>,
+                f: impl for #field_lt_ty_generics ::core::ops::FnOnce(
+                    #projection_ref_lt #ty_generics_with_this_field_ref_lt
+                ) -> R,
+            ) -> R {
+                let #this = ::core::pin::Pin::get_ref(self);
+                f(#projection_ref_lt_init)
+            }
+
             #(#accessors)*
         }
     }

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 20/20] rust: pin-init: internal: pin_data: support existential lifetimes
  2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
                   ` (18 preceding siblings ...)
  2026-10-08 19:24 ` [PATCH v2 19/20] rust: pin-init: internal: pin_data: support shared projection Gary Guo
@ 2026-10-08 19:24 ` Gary Guo
  19 siblings, 0 replies; 21+ messages in thread
From: Gary Guo @ 2026-10-08 19:24 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: linux-kernel, rust-for-linux, Gary Guo

There are many cases where structs that have interior mutability, but they
do not need the invariance over captured lifetimes as the lifetime is
captured upon construction, and new data of that particular lifetime does
not flow back into the struct.

For these use cases, the same mechanism as pin-init self-reference may be
used. Add support for existential lifetimes, introduced by having where
clauses such as

    exists<'a>: 'b

The lifetime `'a` above is minted similar to field lifetimes. Because `'a`
is an erased lifetime living longer than `'b`, the only variance
requirement that we have is that `'b` cannot be contravariant; that defense
is fulfilled by adding `PhantomData<&'b ()>` so the struct is either
covariant or invariant over `'b`.

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/pin-init/internal/src/pin_data.rs | 253 ++++++++++++++++++++++++++++++---
 rust/pin-init/src/__internal.rs        |  15 ++
 2 files changed, 252 insertions(+), 16 deletions(-)

diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 3d7726e12b3b..df2d2fc5aa2f 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -11,8 +11,9 @@
     spanned::Spanned,
     visit::Visit,
     visit_mut::VisitMut,
-    Attribute, Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime,
-    LifetimeParam, Member, Meta, PathSegment, Token, Type, TypePath,
+    Attribute, Field, Fields, GenericArgument, GenericParam, Generics, Ident, Index, Item,
+    ItemStruct, Lifetime, LifetimeParam, Member, Meta, PathArguments, PathSegment, Token, Type,
+    TypeParamBound, TypePath, WhereClause, WherePredicate,
 };
 
 use crate::{
@@ -183,6 +184,7 @@ fn parse_list(
         dcx: &mut DiagCtxt,
         attrs: &mut Vec<Attribute>,
         bound_lifetimes: &BTreeSet<&Lifetime>,
+        exist_lifetimes: &BTreeSet<ExistLt>,
         field_idx_map: &BTreeMap<Ident, usize>,
     ) -> Option<(BTreeSet<Capture>, Variance)> {
         let attr = attrs.extract_single_attr(dcx, "uses")?;
@@ -208,7 +210,10 @@ fn parse_list(
                 continue;
             }
 
-            if lt.ident != "_" && !field_idx_map.contains_key(&lt.ident) {
+            if lt.ident != "_"
+                && !exist_lifetimes.contains(lt)
+                && !field_idx_map.contains_key(&lt.ident)
+            {
                 dcx.error(lt, format!("`{lt}` is not a field name"));
                 continue;
             }
@@ -240,11 +245,15 @@ struct StructInfo {
     field_idx_map: BTreeMap<Ident, usize>,
     is_tuple_struct: bool,
     self_referential: bool,
+    /// Existential lifetimes defined.
+    exist_lts: BTreeSet<ExistLt>,
     /// Field lifetime genercis with outlive chain.
     field_lts_outlive_chain: Generics,
     /// Field lifetime genercis with outlive chain, with one chain for covariant fields and one
     /// chain for invariant fields.
     field_lts_split_variance_outlive_chain: Generics,
+    /// Existential lifetime with their outlives bounds.
+    exist_lts_generics: Generics,
 }
 
 pub(crate) fn expand_with_cfg(
@@ -334,6 +343,12 @@ fn expand(
 
     let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_));
 
+    let exist_lts = if let Some(whr) = &mut struct_.generics.where_clause {
+        ExistLt::parse(dcx, whr)
+    } else {
+        BTreeSet::new()
+    };
+
     // Collect all bound lifetimes from generics.
     let bound_lifetimes: BTreeSet<&Lifetime> =
         struct_.generics.lifetimes().map(|x| &x.lifetime).collect();
@@ -347,6 +362,16 @@ fn expand(
         .filter_map(|(index, field)| Some((field.ident.clone()?, index)))
         .collect();
 
+    for l in &exist_lts {
+        let lt = &l.lifetime;
+        if bound_lifetimes.contains(&lt) {
+            dcx.error(
+                lt,
+                format!("existential `{lt}` conflicts with struct generics"),
+            );
+        }
+    }
+
     // Keep track on fields being implicitly borrowed by being mentioned.
     let mut implicitly_borrowed = BTreeSet::new();
 
@@ -370,9 +395,14 @@ fn expand(
             };
 
             // Parse `#[uses]` attribute.
-            let (mut captures, wildcard_variance) =
-                Capture::parse_list(dcx, &mut field.attrs, &bound_lifetimes, &field_idx_map)
-                    .unwrap_or_default();
+            let (mut captures, wildcard_variance) = Capture::parse_list(
+                dcx,
+                &mut field.attrs,
+                &bound_lifetimes,
+                &exist_lts,
+                &field_idx_map,
+            )
+            .unwrap_or_default();
 
             let mut generic_lt_captures = BTreeSet::new();
             let mut generic_ty_captures = BTreeSet::new();
@@ -399,7 +429,7 @@ fn expand(
                     return;
                 }
 
-                if !field_idx_map.contains_key(&lt.ident) {
+                if !exist_lts.contains(lt) && !field_idx_map.contains_key(&lt.ident) {
                     dcx.error(
                         lt,
                         format!("`{lt}` is neither a lifetime in generics nor a field name"),
@@ -414,8 +444,10 @@ fn expand(
             })
             .visit_type(&field.ty);
 
-            for capture in captures.iter() {
-                implicitly_borrowed.insert(capture.lifetime.ident.clone());
+            for capture in captures.iter(){
+                if !exist_lts.contains(&capture.lifetime){
+                    implicitly_borrowed.insert(capture.lifetime.ident.clone());
+                }
             }
 
             GenericParam::maybe_type_params_visitor(|ident| {
@@ -495,6 +527,9 @@ fn expand(
     let mut worklist = Vec::new();
     for field in fields.iter() {
         for borrow in field.captures.iter() {
+            if exist_lts.contains(&borrow.lifetime) {
+                continue;
+            }
             if let Variance::Invariant = borrow.variance {
                 let Some(borrow_idx) = fields
                     .iter()
@@ -510,6 +545,9 @@ fn expand(
     }
     while let Some(field) = worklist.pop() {
         for borrow in field.captures.iter() {
+            if exist_lts.contains(&borrow.lifetime) {
+                continue;
+            }
             let Some(borrow_idx) = fields
                 .iter()
                 .position(|f| f.member.as_ident() == borrow.lifetime.ident)
@@ -581,6 +619,23 @@ fn expand(
         }
     }
 
+    let exist_lt_generics = Generics {
+        lt_token: Some(Default::default()),
+        params: exist_lts
+            .iter()
+            .map(|l| {
+                GenericParam::Lifetime(LifetimeParam {
+                    attrs: Vec::new(),
+                    lifetime: l.lifetime.clone(),
+                    colon_token: Default::default(),
+                    bounds: l.bounds.iter().cloned().collect(),
+                })
+            })
+            .collect(),
+        gt_token: Some(Default::default()),
+        where_clause: None,
+    };
+
     let mut field_lts_split_variance_outlive_chain = Generics {
         lt_token: Some(Default::default()),
         params: borrowed_covariant_fields
@@ -642,6 +697,16 @@ fn expand(
                 .push(parse_quote!(#ty: #field_lt));
         }
 
+        for borrow in field.captures.iter().rev() {
+            let lt = &borrow.lifetime;
+            if exist_lts.contains(lt) {
+                field_lts_split_variance_outlive_chain
+                    .make_where_clause()
+                    .predicates
+                    .push(parse_quote!(#lt: #field_lt));
+            }
+        }
+
         // If a field is invariant, then the invariance closure rule will make all borrowed fields
         // to be invariant, so they're already captured in `field_lts_split_variance_outlive_chain`.
         if borrowed.lt_variance != Variance::Covariant {
@@ -681,8 +746,10 @@ fn expand(
         fields,
         field_idx_map,
         is_tuple_struct,
+        exist_lts,
         field_lts_outlive_chain,
         field_lts_split_variance_outlive_chain,
+        exist_lts_generics: exist_lt_generics,
     };
 
     for field in &info.fields {
@@ -745,6 +812,129 @@ fn is_phantom_pinned(ty: &Type) -> bool {
     }
 }
 
+struct ExistLt {
+    lifetime: Lifetime,
+    bounds: Vec<Lifetime>,
+}
+
+impl std::borrow::Borrow<Lifetime> for ExistLt {
+    fn borrow(&self) -> &Lifetime {
+        &self.lifetime
+    }
+}
+
+impl PartialEq for ExistLt {
+    fn eq(&self, other: &Self) -> bool {
+        self.lifetime == other.lifetime
+    }
+}
+
+impl Eq for ExistLt {}
+
+impl PartialOrd for ExistLt {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for ExistLt {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.lifetime.cmp(&other.lifetime)
+    }
+}
+
+impl ExistLt {
+    fn parse(dcx: &mut DiagCtxt, whr: &mut WhereClause) -> BTreeSet<ExistLt> {
+        fn parse_one(
+            dcx: &mut DiagCtxt,
+            pred: &WherePredicate,
+            result: &mut BTreeSet<ExistLt>,
+        ) -> bool {
+            let WherePredicate::Type(pred) = &pred else {
+                return false;
+            };
+            let Type::Path(path) = &pred.bounded_ty else {
+                return false;
+            };
+
+            if path.qself.is_some()
+                || path.path.leading_colon.is_some()
+                || path.path.segments.len() != 1
+            {
+                return false;
+            }
+            let path_seg = &path.path.segments[0];
+
+            if path_seg.ident != "exists" {
+                return false;
+            };
+
+            let PathArguments::AngleBracketed(p) = &path_seg.arguments else {
+                dcx.error(
+                    path_seg,
+                    "existential clauses require a single lifetime, e.g. `exists<'a>`",
+                );
+                return true;
+            };
+
+            if p.args.len() != 1 {
+                dcx.error(
+                    path_seg,
+                    "existential clauses require a single lifetime, e.g. `exists<'a>`",
+                );
+                return true;
+            }
+
+            let GenericArgument::Lifetime(lt) = &p.args[0] else {
+                dcx.error(
+                    path_seg,
+                    "existential clauses require a single lifetime, e.g. `exists<'a>`",
+                );
+                return true;
+            };
+
+            if result.contains(lt) {
+                dcx.error(
+                    lt,
+                    format!("an existential clause for `{lt}` already exists"),
+                );
+                return true;
+            }
+
+            let mut bounds = Vec::new();
+            for bound in pred.bounds.iter() {
+                let TypeParamBound::Lifetime(lt) = bound else {
+                    dcx.error(bound, "only lifetime can appear in existential clauses");
+                    return true;
+                };
+                bounds.push(lt.clone());
+            }
+            if bounds.is_empty() {
+                dcx.error(
+                    pred.colon_token,
+                    "existential clauses require at least one outlive bounds",
+                );
+                return true;
+            }
+
+            result.insert(ExistLt {
+                lifetime: lt.clone(),
+                bounds: bounds.clone(),
+            });
+
+            true
+        }
+
+        let mut result = BTreeSet::new();
+        whr.predicates = std::mem::take(&mut whr.predicates)
+            .into_pairs()
+            .filter(|p| !parse_one(dcx, p.value(), &mut result))
+            .collect();
+
+        result
+    }
+}
+
 fn generate_struct_def(info: &StructInfo) -> TokenStream {
     let ItemStruct {
         attrs,
@@ -783,12 +973,23 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream {
             // implementation and thus may be used inside `Erase`.
             ty = quote!((#ty,));
 
+            let mut exist_lt_phantoms = Vec::new();
             for borrow in field.captures.iter().rev() {
                 let lt = &borrow.lifetime;
                 ty = quote!(for<#lt> fn(&#lt()) -> #ty);
+
+                if let Some(exist_lt) = info.exist_lts.get(lt) {
+                    for bound in &exist_lt.bounds {
+                        exist_lt_phantoms.push(quote!(::pin_init::__internal::ExistLt<#bound>));
+                    }
+                }
             }
 
             ty = quote!(::pin_init::__internal::Erase<#ty>);
+
+            if !exist_lt_phantoms.is_empty() {
+                ty = quote!(::pin_init::__internal::WithPhantom<#ty, (#(#exist_lt_phantoms,)*)>);
+            }
         };
 
         if let Some(borrowed) = &field.borrowed {
@@ -998,8 +1199,11 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre
     // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types
     // are wellformed, given the bounds that we understand.
 
-    let generics_with_field_lt =
-        CombinedGenerics(vec![&info.field_lts_split_variance_outlive_chain, generics]);
+    let generics_with_field_lt = CombinedGenerics(vec![
+        &info.exist_lts_generics,
+        &info.field_lts_split_variance_outlive_chain,
+        generics,
+    ]);
 
     let (_, ty_generics, _) = generics.split_for_impl();
     let (impl_generics_with_field_lt, _, whr_with_field_lt) =
@@ -1143,20 +1347,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
 
     // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
     // used with `for`.
-    let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]);
+    let field_lts = CombinedGenerics(vec![
+        &info.field_lts_outlive_chain,
+        &info.exist_lts_generics,
+    ]);
     let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
     let generics_with_this_field_lt = CombinedGenerics(vec![
         &this_lt_generics,
+        &info.exist_lts_generics,
         &info.field_lts_outlive_chain,
         generics,
     ]);
     let generics_with_this_field_ref_lt = CombinedGenerics(vec![
         &this_lt_generics,
+        &info.exist_lts_generics,
         &info.field_lts_split_variance_outlive_chain,
         generics,
     ]);
-    let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
+    let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl();
     let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl();
     let (_, ty_generics_with_this_field_ref_lt, _) =
@@ -1670,10 +1879,19 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
     // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be
     // used with `for`.
     let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]);
-    let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]);
+    let field_exist_lts = CombinedGenerics(vec![
+        &info.field_lts_outlive_chain,
+        &info.exist_lts_generics,
+    ]);
+    let generics_with_field_lt = CombinedGenerics(vec![
+        &info.field_lts_outlive_chain,
+        &info.exist_lts_generics,
+        generics,
+    ]);
 
     let (impl_generics, ty_generics, whr) = generics.split_for_impl();
     let (_, field_lt_ty_generics, _) = field_lts.split_for_impl();
+    let (_, field_exist_lt_ty_generics, _) = field_exist_lts.split_for_impl();
     let (impl_generics_with_lt, ty_generics_with_field_lt, whr_with_field_lt) =
         generics_with_field_lt.split_for_impl();
 
@@ -1783,6 +2001,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream {
         })
         .collect::<TokenStream>();
 
+    let exist_lt_generics_params = info.exist_lts_generics.params.iter();
     quote! {
         // We declare this struct which will host all of the projection function for our type.
         #[doc(hidden)]
@@ -1838,7 +2057,7 @@ impl #impl_generics __ThePinData #ty_generics
         {
             /// Type inference helper function.
             #[inline(always)]
-            #vis fn __make_closure<__F, __E>(self, f: __F) -> __F
+            #vis fn __make_closure<#(#exist_lt_generics_params,)* __F, __E>(self, f: __F) -> __F
             where
                 __F: for #field_lt_ty_generics ::core::ops::FnOnce(
                     *mut #struct_name #ty_generics,
@@ -1849,7 +2068,9 @@ impl #impl_generics __ThePinData #ty_generics
             }
 
             #[inline(always)]
-            #vis fn __with_lt #field_lts(self) -> __PinDataLt #ty_generics_with_field_lt {
+            #vis fn __with_lt #field_exist_lt_ty_generics(self)
+                -> __PinDataLt #ty_generics_with_field_lt
+            {
                 // Generate a zeroed to avoid naming all fields.
                 // SAFETY: `__PinDataLt` only contains phantom fields.
                 unsafe { ::core::mem::zeroed() }
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index 516dd893746e..b9aac6e4bd37 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -725,3 +725,18 @@ pub fn new() -> Self {
         Self(PhantomData)
     }
 }
+
+/// Marker type to capture outlive bounds coming from existential lifetimes.
+pub struct ExistLt<'a>(PhantomData<&'a ()>);
+
+// Dummy `Drop`, same reason as `Borrowed`.
+impl Drop for ExistLt<'_> {
+    #[inline(always)]
+    fn drop(&mut self) {}
+}
+
+/// Type that allows additional `PhantomData` to be attached.
+///
+/// This allows changing variance of types without introducing additional fields.
+#[repr(transparent)]
+pub struct WithPhantom<T, P>(PhantomData<P>, T);

-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-10-08 19:27 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-08 19:24 [PATCH v2 00/20] rust: pin-init: create self references safely Gary Guo
2026-10-08 19:24 ` [PATCH v2 01/20] kbuild: rust: allow `clippy::comparison_chain` globally Gary Guo
2026-10-08 19:24 ` [PATCH v2 02/20] rust: pin-init: internal: pin_data: infer self-referential struct Gary Guo
2026-10-08 19:24 ` [PATCH v2 03/20] rust: pin-init: internal: pin_data: rewrite fields that borrow others Gary Guo
2026-10-08 19:24 ` [PATCH v2 04/20] rust: pin-init: internal: pin_data: pin borrowed fields with wrapper Gary Guo
2026-10-08 19:24 ` [PATCH v2 05/20] rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle Gary Guo
2026-10-08 19:24 ` [PATCH v2 06/20] rust: pin-init: internal: pin_data: self-referential drop order checks Gary Guo
2026-10-08 19:24 ` [PATCH v2 07/20] rust: pin-init: internal: pin_data: check covariance of self-referential fields Gary Guo
2026-10-08 19:24 ` [PATCH v2 08/20] rust: pin-init: internal: pin_data: implement initialization of borrowed structs Gary Guo
2026-10-08 19:24 ` [PATCH v2 09/20] rust: pin-init: internal: pin_data: project self-referential fields Gary Guo
2026-10-08 19:24 ` [PATCH v2 10/20] rust: pin-init: internal: pin_data: add `with_project` method Gary Guo
2026-10-08 19:24 ` [PATCH v2 11/20] rust: pin-init: internal: pin_data: enable self-referential support Gary Guo
2026-10-08 19:24 ` [PATCH v2 12/20] rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order Gary Guo
2026-10-08 19:24 ` [PATCH v2 13/20] rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation Gary Guo
2026-10-08 19:24 ` [PATCH v2 14/20] rust: pin-init: internal: pin_data: support mutable borrows Gary Guo
2026-10-08 19:24 ` [PATCH v2 15/20] rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation Gary Guo
2026-10-08 19:24 ` [PATCH v2 16/20] rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance Gary Guo
2026-10-08 19:24 ` [PATCH v2 17/20] rust: pin-init: internal: pin_data: complete invariant borrow support Gary Guo
2026-10-08 19:24 ` [PATCH v2 18/20] rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible Gary Guo
2026-10-08 19:24 ` [PATCH v2 19/20] rust: pin-init: internal: pin_data: support shared projection Gary Guo
2026-10-08 19:24 ` [PATCH v2 20/20] rust: pin-init: internal: pin_data: support existential lifetimes Gary Guo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®