From 71b0637185270a657333bb378d36e6bc79d64518 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 26 Sep 2026 00:25:21 +0100 Subject: [PATCH] rust: pin-init: internal: pin_data: project self-referential fields 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. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-9-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- 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 a46c4dcd7da5..002489a9b36c 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::{ @@ -844,7 +844,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(); @@ -855,33 +856,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 field.pinned { + // 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 = 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 !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, ), ) } @@ -931,6 +967,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 @@ -957,6 +1036,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 b639c9204a7d..d44a5b1f3362 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(PhantomData); + +impl NotVisible { + #[inline(always)] + pub fn new() -> Self { + Self(PhantomData) + } +}