From: Gary Guo <gary@garyguo.net>
To: "Benno Lossin" <lossin@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Gary Guo <gary@garyguo.net>
Subject: [PATCH v2 09/20] rust: pin-init: internal: pin_data: project self-referential fields
Date: Thu, 08 Oct 2026 20:24:33 +0100 [thread overview]
Message-ID: <20261008-dev-selfref-v2-9-e280b3c8fba5@garyguo.net> (raw)
In-Reply-To: <20261008-dev-selfref-v2-0-e280b3c8fba5@garyguo.net>
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
next prev parent reply other threads:[~2026-10-08 19:27 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Gary Guo [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261008-dev-selfref-v2-9-e280b3c8fba5@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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®