From 44e77f14eda37300508a0348cd6d97cbb02d11c7 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 30 Apr 2026 18:49:47 +0100 Subject: [PATCH 01/20] internal: pin_data: infer self-referential struct 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. Signed-off-by: Gary Guo --- internal/Cargo.toml | 2 +- internal/src/pin_data.rs | 162 ++++++++++++++++++++++++++++++++++++++- internal/src/util.rs | 94 ++++++++++++++++++++++- 3 files changed, 254 insertions(+), 4 deletions(-) diff --git a/internal/Cargo.toml b/internal/Cargo.toml index e2714fb3..81e668e9 100644 --- a/internal/Cargo.toml +++ b/internal/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] quote = "1.0.40" proc-macro2 = "1.0.101" -syn = { version = "2.0.106", features = ["full", "parsing", "visit-mut"] } +syn = { version = "2.0.106", features = ["full", "parsing", "visit", "visit-mut"] } [build-dependencies] rustc_version = "0.4" diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 03e893cf..cf6142cd 100644 --- a/internal/src/pin_data.rs +++ b/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 @@ use syn::{ 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 @@ impl ToTokens for Args { } } +/// 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 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 { + 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, + captures: BTreeSet, } struct StructInfo { @@ -59,6 +122,7 @@ struct StructInfo { struct_: ItemStruct, fields: Vec, 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 = 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 = 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 = 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(<.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(<) { + return; + } + + if field_idx_map.contains_key(<.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/internal/src/util.rs b/internal/src/util.rs index 3ce498ce..67ebb333 100644 --- a/internal/src/util.rs +++ b/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 @@ impl ToTokens for CombinedTypeGenerics<'_> { .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(&<.lifetime) { + self.bound.insert(<.lifetime); + to_remove.push(<.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); + }); + } +} From d3e89e0799ec5a87b32a545560df599bacbcae6f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 18:32:29 +0100 Subject: [PATCH 02/20] internal: pin_data: rewrite fields that borrow others 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 --- internal/src/pin_data.rs | 16 +++++++++ src/__internal.rs | 77 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index cf6142cd..36678843 100644 --- a/internal/src/pin_data.rs +++ b/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/src/__internal.rs b/src/__internal.rs index cba53b8c..0a824747 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -385,3 +385,80 @@ unsafe impl PinInit for AlwaysFail { Err(()) } } +/// Polyfill of `FnOnce` trait to be able to reference output via associated type. +pub trait FnOutput { + type Output; +} + +macro_rules! impl_fn_output { + () => {}; + ($ret:ident, $($arg:ident,)*) => { + impl 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 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::Erased); + +/// Helper trait to resolve the erased lifetime. +trait EraseLt { + type Erased; +} + +impl EraseLt for (T,) { + type Erased = T; +} + +impl EraseLt for T +where + T: for<'a> FnOutput<(&'a (),), Output: EraseLt>, +{ + type Erased = <>::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 Send for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Send` for across *all* that +// lifetimes. +unsafe impl Send for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Send, +{ +} + +// SAFETY: Trivial, no lifetime to erase. +unsafe impl Sync for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Sync` for across *all* that +// lifetimes. +unsafe impl Sync for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Sync, +{ +} From ab3dce43a0b2adf9be42cfbf69184c88e3759376 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 22 Sep 2026 21:42:34 +0100 Subject: [PATCH 03/20] internal: pin_data: pin borrowed fields with wrapper 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. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 19 +++++++++++++++++++ src/__internal.rs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 36678843..a3e492c4 100644 --- a/internal/src/pin_data.rs +++ b/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 . + 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/src/__internal.rs b/src/__internal.rs index 0a824747..74dc2350 100644 --- a/src/__internal.rs +++ b/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 @@ where for<'a> Erase<>::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(PhantomPinned, T); + +impl Deref for Borrowed { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + &self.1 + } +} From 897485faf44e2ff61c7aaf18e45d54d8693810ab Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 22 Sep 2026 22:58:11 +0100 Subject: [PATCH 04/20] internal: pin_data: teach drop check about generics that cannot dangle 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. Signed-off-by: Gary Guo --- src/__internal.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/__internal.rs b/src/__internal.rs index 74dc2350..32bd6d8c 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -473,6 +473,35 @@ where #[repr(transparent)] pub struct Borrowed(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 Drop for Borrowed { + #[inline(always)] + fn drop(&mut self) {} +} + impl Deref for Borrowed { type Target = T; From feae46e60f44c02dd5b79c4e1cd495c69ddef9c6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 20:50:50 +0100 Subject: [PATCH 05/20] internal: pin_data: self-referential drop order checks 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 --- internal/src/pin_data.rs | 207 ++++++++++++++++++++++++++++++++++++++- internal/src/util.rs | 65 +++++++++++- 2 files changed, 263 insertions(+), 9 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index a3e492c4..ae0e169a 100644 --- a/internal/src/pin_data.rs +++ b/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 @@ use syn::{ 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, captures: BTreeSet, + generic_lt_captures: BTreeSet, + generic_ty_captures: BTreeSet, } struct StructInfo { args: Args, struct_: ItemStruct, fields: Vec, + field_idx_map: BTreeMap, 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 = 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,123 @@ fn generate_drop_impl(info: &StructInfo) -> TokenStream { } } +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 span = ty.span().resolved_at(Span::mixed_site()); + let ident = f.member.as_ident(); + let lt = f.borrowed.as_ref().map(|b| &b.lifetime); + quote_spanned!(span => + let #ident: &#lt mut #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/internal/src/util.rs b/internal/src/util.rs index 67ebb333..f19712a4 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -5,7 +5,8 @@ use std::collections::BTreeSet; 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 @@ impl MemberExt for Member { 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 @@ impl CombinedGenerics<'_> { ) -> ( 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 @@ impl ToTokens for CombinedTypeGenerics<'_> { } } +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 @@ impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> { }); } } + +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); + + impl<'a, F> Visit<'a> for TypeParamVisitor + 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) + } +} From 17e5fc8e1649575216c685aef12f699af9c6c8b4 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:08:21 +0100 Subject: [PATCH 06/20] internal: pin_data: check covariance of self-referential fields 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 --- internal/src/pin_data.rs | 75 +++++++++++++++++++++++++++++++++++++++- internal/src/util.rs | 24 +++++++++++-- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ae0e169a..84a1fbac 100644 --- a/internal/src/pin_data.rs +++ b/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 @@ -765,6 +766,78 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre } } +/// 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/internal/src/util.rs b/internal/src/util.rs index f19712a4..59054f59 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -5,8 +5,8 @@ use std::collections::BTreeSet; 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 @@ impl GenericParamExt for GenericParam { 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 + ) + } +} From 237b17a6649dbf52a72a382bedb63c1d91d0bb07 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 25 Apr 2026 18:27:37 +0100 Subject: [PATCH 07/20] internal: pin_data: implement initialization of borrowed structs 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. Signed-off-by: Gary Guo --- internal/src/init.rs | 42 +++- internal/src/pin_data.rs | 114 +++++++-- src/__internal.rs | 169 +++++++++++++- src/lib.rs | 1 + .../init/tuple_invalid_field.stderr | 2 +- tests/ui/expand/many_generics.expanded.rs | 79 +++++-- tests/ui/expand/pin-data.expanded.rs | 57 +++-- tests/ui/expand/pinned_drop.expanded.rs | 57 +++-- tests/ui/expand/simple-init.expanded.rs | 5 +- tests/ui/expand/tuple_struct.expanded.rs | 218 ++++++++++++------ 10 files changed, 594 insertions(+), 150 deletions(-) diff --git a/internal/src/init.rs b/internal/src/init.rs index 80e4dc06..ded2b528 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -288,8 +288,10 @@ fn expand( }, }; // `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 expand( // 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, pinned: bool) -> TokenStream { +fn make_field_init( + fields: &Punctuated, + 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, 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, 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, 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/internal/src/pin_data.rs b/internal/src/pin_data.rs index 84a1fbac..a46c4dcd 100644 --- a/internal/src/pin_data.rs +++ b/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. @@ -969,12 +968,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() @@ -987,6 +1010,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 /// @@ -1001,19 +1048,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::(); + 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 @@ -1032,7 +1114,6 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #whr {} - #[allow(dead_code)] // Some functions might never be used and private. impl #impl_generics __ThePinData #ty_generics #whr { @@ -1040,13 +1121,20 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #[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/src/__internal.rs b/src/__internal.rs index 32bd6d8c..b639c920 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -109,10 +109,15 @@ impl InitData { #[inline(always)] pub fn __make_closure(self, f: F) -> F where - F: FnOnce(*mut T) -> Result, + F: FnOnce(*mut T, Self) -> Result, { 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 @@ impl DropGuard { // 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 DropGuard { @@ -332,6 +343,12 @@ impl DropGuard { // 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 Drop for DropGuard { @@ -342,6 +359,156 @@ impl Drop for DropGuard { } } +/// 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(self, init: impl Init) -> Result, E> { + // SAFETY: + // - `self.ptr` is valid and properly aligned. + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // 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(self, init: impl PinInit) -> Result, 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` (only `Pin<&mut T>`). + 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 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/src/lib.rs b/src/lib.rs index d1ed0561..9ffa7643 100644 --- a/src/lib.rs +++ b/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()); }; diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.stderr b/tests/ui/compile-fail/init/tuple_invalid_field.stderr index 9ed771c6..a70f0db2 100644 --- a/tests/ui/compile-fail/init/tuple_invalid_field.stderr +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -1,4 +1,4 @@ -error[E0599]: no method named `_2` found for struct `__ThePinData` in the current scope +error[E0599]: no method named `_2` found for struct `__PinDataLt` in the current scope --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 | 3 | #[pin_data] diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 5c0ef341..3af8ba0e 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -57,47 +57,41 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + #[allow(non_snake_case)] + struct __PinDataLt<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { - __phantom: ::pin_init::__internal::PhantomInvariant>, + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + r: ::pin_init::__internal::PhantomInvariant<&'b mut [&'a mut T; SIZE]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData>, } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { - #[inline] fn clone(&self) -> Self { *self } } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, {} #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] impl< 'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize, - > __ThePinData<'a, 'b, T, SIZE> + > __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo<'a, 'b, T, SIZE>, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } /// # Safety /// /// - `slot` is valid and properly aligned. @@ -113,7 +107,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -130,7 +124,7 @@ const _: () = { ::pin_init::__internal::Unpinned, &'b mut [&'a mut T; SIZE], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r as _) } } /// # Safety /// @@ -147,7 +141,54 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + where + T: Bar<'a, 1>, + { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + {} + impl< + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize, + > __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo<'a, 'b, T, SIZE>, + __PinDataLt<'a, 'b, T, SIZE>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt<'a, 'b, T, SIZE> { + unsafe { ::core::mem::zeroed() } } } unsafe impl< diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 3c8f88e3..f9439a24 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -34,28 +34,21 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + #[allow(non_snake_case)] + struct __PinDataLt { + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData, } - impl ::core::clone::Clone for __ThePinData { - #[inline] + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +64,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -88,7 +81,35 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + unsafe { ::core::mem::zeroed() } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 7dfe06f4..6e70e346 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -34,28 +34,21 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + #[allow(non_snake_case)] + struct __PinDataLt { + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData, } - impl ::core::clone::Clone for __ThePinData { - #[inline] + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +64,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -88,7 +81,35 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + unsafe { ::core::mem::zeroed() } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index fa621a24..c90efb08 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -10,7 +10,8 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { + >(move |slot, data_lt| { + if true {} else {} #[allow(unreachable_code)] let _ = || unsafe { ::core::ptr::write(slot, Foo {}) }; Ok(unsafe { ::pin_init::__internal::InitOk::new() }) @@ -18,7 +19,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index 6c913f13..f1bd9d6a 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -34,28 +34,22 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData<'a, T: Copy, const N: usize> { - __phantom: ::pin_init::__internal::PhantomInvariant>, + #[allow(non_snake_case)] + struct __PinDataLt<'a, T: Copy, const N: usize> { + _0: ::pin_init::__internal::PhantomInvariant<&'a mut [T; N]>, + _1: ::pin_init::__internal::PhantomInvariant, + _2: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData>, } - impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { - #[inline] + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __PinDataLt<'a, T, N> { fn clone(&self) -> Self { *self } } - impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __PinDataLt<'a, T, N> {} #[allow(dead_code)] - impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo<'a, T, N>, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl<'a, T: Copy, const N: usize> __PinDataLt<'a, T, N> { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +65,7 @@ const _: () = { ::pin_init::__internal::Unpinned, &'a mut [T; N], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0 as _) } } /// # Safety /// @@ -88,7 +82,7 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1 as _) } } /// # Safety /// @@ -102,7 +96,35 @@ const _: () = { self, slot: *mut Foo<'a, T, N>, ) -> ::pin_init::__internal::Slot<::pin_init::__internal::Unpinned, usize> { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2 as _) } + } + } + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize> { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo<'a, T, N>, + __PinDataLt<'a, T, N>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt<'a, T, N> { + unsafe { ::core::mem::zeroed() } } } unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData @@ -149,31 +171,58 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { - let mut ___0_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).0) - }) - .write(&mut first); - let mut ___1_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).1) - }) - .write(PhantomPinned); - let mut ___2_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).2) - }) - .init(10)?; - ::core::mem::forget(___2_guard); - ::core::mem::forget(___1_guard); - ::core::mem::forget(___0_guard); + >(move |slot, data_lt| { + if true { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } else { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } #[allow(unreachable_code)] let _ = || unsafe { let _ = &(*slot).0; @@ -193,7 +242,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; @@ -207,31 +256,58 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { - let mut ___0_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).0) - }) - .write(&mut second); - let mut ___1_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).1) - }) - .write(PhantomPinned); - let mut ___2_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).2) - }) - .write(20); - ::core::mem::forget(___2_guard); - ::core::mem::forget(___1_guard); - ::core::mem::forget(___0_guard); + >(move |slot, data_lt| { + if true { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } else { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } #[allow(unreachable_code)] let _ = || unsafe { let _ = &(*slot).0; @@ -251,7 +327,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; From 9ee880329b98ecbfb67c078305cb263e33b99159 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 26 Sep 2026 00:25:21 +0100 Subject: [PATCH 08/20] 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. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 109 ++++++++++++++++++++++++++++++++++----- src/__internal.rs | 12 +++++ 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index a46c4dcd..002489a9 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -12,7 +12,7 @@ use syn::{ 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 `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 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, ), ) } @@ -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 @@ fn generate_projections(info: &StructInfo) -> TokenStream { let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; #projection_init } + + #(#accessors)* } } } diff --git a/src/__internal.rs b/src/__internal.rs index b639c920..d44a5b1f 100644 --- a/src/__internal.rs +++ b/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 @@ impl Deref for Borrowed { &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) + } +} From ca0985e3b4c6912d72f087924c6ecbd5cb790c80 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:29:08 +0100 Subject: [PATCH 09/20] internal: pin_data: add `with_project` method 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 --- internal/src/pin_data.rs | 141 +++++++++++++++++++- tests/ui/compile-fail/pin_data/twice.stderr | 10 ++ tests/ui/expand/many_generics.expanded.rs | 41 ++++++ tests/ui/expand/pin-data.expanded.rs | 27 ++++ tests/ui/expand/pinned_drop.expanded.rs | 27 ++++ tests/ui/expand/tuple_struct.expanded.rs | 32 +++++ 6 files changed, 273 insertions(+), 5 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 002489a9..78044cbc 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -846,10 +846,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"); @@ -923,16 +930,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 = 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 { ( @@ -968,6 +1037,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. // @@ -1018,6 +1123,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 { @@ -1037,6 +1149,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #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)* } } diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index 562177ea..4c56344a 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -27,3 +27,13 @@ error[E0592]: duplicate definitions with name `project` | ^^^^^^^^^^^ duplicate definitions for `project` | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `with_project` + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 + | +3 | #[pin_data] + | ----------- other definition for `with_project` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `with_project` + | + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 3af8ba0e..acd6f229 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -31,6 +31,26 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo<'a, 'b, T, SIZE>>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt< + '__this, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__this mut [u8; 1024 * 1024], + r: &'__this mut &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData< + &'__this mut Foo<'a, 'b, T, SIZE>, + >, + } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> where T: Bar<'a, 1>, @@ -55,6 +75,27 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this, 'a, 'b, T, SIZE>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + r: &mut this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index f9439a24..5945bfc3 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -13,6 +13,14 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this> { + array: &'__this mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -32,6 +40,25 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 6e70e346..6ad52bdf 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -13,6 +13,14 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this> { + array: &'__this mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -32,6 +40,25 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index f1bd9d6a..0c96de92 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -11,6 +11,15 @@ const _: () = { &'__this mut usize, ::core::marker::PhantomData<&'__this mut Foo<'a, T, N>>, ); + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this, 'a, T: Copy, const N: usize>( + &'__this mut &'a mut [T; N], + ::core::pin::Pin<&'__this mut PhantomPinned>, + &'__this mut usize, + ::core::marker::PhantomData<&'__this mut Foo<'a, T, N>>, + ); impl<'a, T: Copy, const N: usize> Foo<'a, T, N> { /// Pin-projects all fields of `Self`. /// @@ -32,6 +41,29 @@ const _: () = { ::core::marker::PhantomData, ) } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this, 'a, T, N>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f( + __ProjectionLt( + &mut this.0, + unsafe { ::core::pin::Pin::new_unchecked(&mut this.1) }, + &mut this.2, + ::core::marker::PhantomData, + ), + ) + } } #[doc(hidden)] #[allow(non_snake_case)] From b66eb3a0f7385243a9f06f54af8c3773996dd2d5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 29 Sep 2026 13:58:15 +0100 Subject: [PATCH 10/20] internal: pin_data: enable self-referential support Enable self-referential support, and add example and test cases. Signed-off-by: Gary Guo --- CHANGELOG.md | 1 + examples/selfref.rs | 35 ++++++++++ internal/src/pin_data.rs | 7 -- .../compile-fail/init/selfref_may_dangle.rs | 30 +++++++++ .../init/selfref_may_dangle.stderr | 16 +++++ .../pin_data/selfref_always_pin.rs | 25 +++++++ .../pin_data/selfref_always_pin.stderr | 33 +++++++++ .../pin_data/selfref_covariant_check.rs | 9 +++ .../pin_data/selfref_covariant_check.stderr | 15 +++++ .../compile-fail/pin_data/selfref_dropck.rs | 34 ++++++++++ .../pin_data/selfref_dropck.stderr | 22 ++++++ .../pin_data/selfref_lifetime_specialize.rs | 38 +++++++++++ .../selfref_lifetime_specialize.stderr | 67 +++++++++++++++++++ .../selfref_not_living_long_enough.rs | 16 +++++ .../selfref_not_living_long_enough.stderr | 15 +++++ .../pin_data/selfref_wrong_owner.rs | 27 ++++++++ .../pin_data/selfref_wrong_owner.stderr | 20 ++++++ 17 files changed, 403 insertions(+), 7 deletions(-) create mode 100644 examples/selfref.rs create mode 100644 tests/ui/compile-fail/init/selfref_may_dangle.rs create mode 100644 tests/ui/compile-fail/init/selfref_may_dangle.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_always_pin.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_always_pin.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_covariant_check.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_dropck.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_dropck.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9cf32d..9adbee33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Tuple structs are now supported. For `[pin_]init!` , if pinning (`<-` syntax) is required, only the struct syntax can be used, e.g. `init!(Foo { 0: value, 1 <- initializer })`. +- Safely creating references from sibling fields is now supported. - `[pin_]init_scope` functions to run arbitrary code inside of an initializer. - `&'static mut MaybeUninit` now implements `InPlaceWrite`. This enables users to use external allocation mechanisms such as `static_cell`. diff --git a/examples/selfref.rs b/examples/selfref.rs new file mode 100644 index 00000000..b5cdf96b --- /dev/null +++ b/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/internal/src/pin_data.rs b/internal/src/pin_data.rs index 78044cbc..ad98bb9f 100644 --- a/internal/src/pin_data.rs +++ b/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); diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.rs b/tests/ui/compile-fail/init/selfref_may_dangle.rs new file mode 100644 index 00000000..48654b2c --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_may_dangle.rs @@ -0,0 +1,30 @@ +use pin_init::*; +use std::convert::Infallible; +use std::fmt::Display; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct SelfRef<'a> { + part: PrintOnDrop<&'outer String>, + outer: &'a String, +} + +fn new<'a>(str: &'a String) -> impl PinInit, Infallible> { + pin_init!(SelfRef { + outer: str, + part: PrintOnDrop(*outer), + }) +} + +fn main() { + let str = "hello world".to_owned(); + let _selfref = Box::pin_init(new(&str)).unwrap(); + drop(str); +} diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.stderr b/tests/ui/compile-fail/init/selfref_may_dangle.stderr new file mode 100644 index 00000000..60d35aa0 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_may_dangle.stderr @@ -0,0 +1,16 @@ +error[E0505]: cannot move out of `str` because it is borrowed + --> tests/ui/compile-fail/init/selfref_may_dangle.rs:29:10 + | +27 | let str = "hello world".to_owned(); + | --- binding `str` declared here +28 | let _selfref = Box::pin_init(new(&str)).unwrap(); + | ---- borrow of `str` occurs here +29 | drop(str); + | ^^^ move out of `str` occurs here +30 | } + | - borrow might be used here, when `_selfref` is dropped and runs the destructor for type `Pin>>` + | +help: consider cloning the value if the performance cost is acceptable + | +28 | let _selfref = Box::pin_init(new(&str.clone())).unwrap(); + | ++++++++ diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.rs b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs new file mode 100644 index 00000000..7abef5aa --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs @@ -0,0 +1,25 @@ +// Ensure that types that self-references are always pinned. + +use pin_init::*; + +#[pin_data] +struct Bar { + b: &'f u32, + f: u32, +} + +#[pin_data] +struct Baz { + b: &'f u32, + f: u32, +} + +// Manual implementation must fail. +impl Unpin for Baz {} + +fn assert_unpin() {} + +fn main() { + // All of the below checks must fail. + assert_unpin::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr new file mode 100644 index 00000000..0b6f4462 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr @@ -0,0 +1,33 @@ +error[E0119]: conflicting implementations of trait `Unpin` for type `Baz` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:11:1 + | +11 | #[pin_data] + | ^^^^^^^^^^^ conflicting implementation for `Baz` +... +18 | impl Unpin for Baz {} + | ------------------ first implementation here + | + = note: upstream crates may add a new impl of trait `std::marker::Unpin` for type `std::marker::PhantomPinned` in future versions + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:24:20 + | +24 | assert_unpin::(); + | ^^^ the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required for `Bar` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:5:1 + | + 5 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here + 6 | struct Bar { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:20:20 + | +20 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs new file mode 100644 index 00000000..d471822e --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + not_cov: Box bool + 'str>, + str: String, +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr new file mode 100644 index 00000000..1bceaa19 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_covariant_check.rs:5:14 + | +3 | #[pin_data] + | ----------- in this attribute macro expansion +4 | struct SelfRef { +5 | not_cov: Box bool + 'str>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | lifetime `'__short` defined here + | lifetime `'__long` defined here + | function was supposed to return data with lifetime `'__long` but it is returning data with lifetime `'__short` + | + = help: consider adding the following bound: `'__short: '__long` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.rs b/tests/ui/compile-fail/pin_data/selfref_dropck.rs new file mode 100644 index 00000000..6c7b9d47 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.rs @@ -0,0 +1,34 @@ +use pin_init::*; + +#[pin_data] +struct WrongDropOrder { + b: u32, + ptr: &'b u32, +} + +struct PrintOnDrop<'a>(&'a str); + +impl<'a> Drop for PrintOnDrop<'a> { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct UnsoundImplied { + // Provides an implied bound that `a` outlives `b`! + ptr: &'b &'a (), + a: String, + cannot_refer_a: PrintOnDrop<'b>, + b: String, +} + +fn main() { + let _foo = Box::pin_init(pin_init!(UnsoundImplied { + ptr: &&(), + a: "hello".to_owned(), + cannot_refer_a: PrintOnDrop(a), + b: "world".to_owned(), + })) + .unwrap(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.stderr b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr new file mode 100644 index 00000000..ef87016f --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr @@ -0,0 +1,22 @@ +error: field `ptr` borrows `b`, but drops later + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:6:11 + | +6 | ptr: &'b u32, + | ^^ + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:20:10 + | +17 | #[pin_data] + | ----------- in this attribute macro expansion +... +20 | ptr: &'b &'a (), + | ^^^^^^^^^^ requires that `'a` must outlive `'b` +21 | a: String, + | - lifetime `'a` defined here +22 | cannot_refer_a: PrintOnDrop<'b>, +23 | b: String, + | - lifetime `'b` defined here + | + = help: consider adding the following bound: `'a: 'b` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs new file mode 100644 index 00000000..442cdf43 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs @@ -0,0 +1,38 @@ +// Ensure that types that have impl that specialize on a single lifetime can be used to exploit +// pin-init. + +use std::marker::PhantomData; + +use pin_init::*; + +struct LtSpec<'a>(PhantomData<*const &'a u32>); +struct LtSpec2<'a, 'b>(PhantomData<*const &'a &'b u32>); + +unsafe impl Send for LtSpec<'static> {} +unsafe impl Sync for LtSpec<'static> {} +unsafe impl<'a> Send for LtSpec2<'a, 'a> {} +unsafe impl<'a> Sync for LtSpec2<'a, 'a> {} + +#[pin_data] +struct Foo { + lt_spec: LtSpec<'a>, + a: u32, +} + +#[pin_data] +struct Bar { + lt_spec2: LtSpec2<'a, 'b>, + a: u32, + b: u32, +} + +fn assert_send() {} +fn assert_sync() {} + +fn main() { + // All of the below checks must fail. + assert_send::(); + assert_sync::(); + assert_send::(); + assert_sync::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr new file mode 100644 index 00000000..ced33018 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr @@ -0,0 +1,67 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:24:15 + | +22 | #[pin_data] + | ----------- in this attribute macro expansion +23 | struct Bar { +24 | lt_spec2: LtSpec2<'a, 'b>, + | ^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a` +25 | a: u32, + | - lifetime `'a` defined here +26 | b: u32, + | - lifetime `'b` defined here + | + = help: consider adding the following bound: `'b: 'a` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: higher-ranked lifetime error + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:34:5 + | +34 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: could not prove `Foo: Send` + +error: higher-ranked lifetime error + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:35:5 + | +35 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: could not prove `Foo: Sync` + +error: lifetime bound not satisfied + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:36:5 + | +36 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the lifetime `'a` defined here... + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Send, + | ^^ +note: ...must outlive the lifetime `'a` defined here + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Send, + | ^^ + = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) + +error: lifetime bound not satisfied + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:37:5 + | +37 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the lifetime `'a` defined here... + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Sync, + | ^^ +note: ...must outlive the lifetime `'a` defined here + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Sync, + | ^^ + = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs new file mode 100644 index 00000000..d2b87d6e --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs @@ -0,0 +1,16 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'foo str, + foo: String, +} + +fn self_ref(outer: &str) { + stack_pin_init!(let foo = pin_init!(SelfRef { + foo: "hello world".to_owned(), + part: &outer[..5], + })); +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr new file mode 100644 index 00000000..f3cf6f9f --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr @@ -0,0 +1,15 @@ +error[E0521]: borrowed data escapes outside of function + --> tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs:12:9 + | + 9 | fn self_ref(outer: &str) { + | ----- - let's call the lifetime of this reference `'1` + | | + | `outer` is only valid in the function body +... +12 | part: &outer[..5], + | ^^^^ + | | + | `outer` escapes the function body here + | argument requires that `'1` must outlive `'static` + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs new file mode 100644 index 00000000..6ed3ddc1 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs @@ -0,0 +1,27 @@ +use pin_init::*; +use std::fmt::Display; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct MyStruct { + borrow: &'owner String, + owner: String, + print_on_drop: PrintOnDrop<&'later_owner String>, + later_owner: String, +} + +fn main() { + stack_pin_init!(let x = pin_init!(MyStruct { + owner: "hello world".to_owned(), + borrow: owner, + later_owner: "hello world".to_owned(), + print_on_drop: PrintOnDrop(owner), + })); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr new file mode 100644 index 00000000..71185427 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr @@ -0,0 +1,20 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs:25:9 + | +21 | stack_pin_init!(let x = pin_init!(MyStruct { + | _____________________________- +22 | | owner: "hello world".to_owned(), +23 | | borrow: owner, +24 | | later_owner: "hello world".to_owned(), +25 | | print_on_drop: PrintOnDrop(owner), + | | ^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2` +26 | | })); + | | - + | | | + | |______has type `__PinDataLt<'1, '_>` + | has type `__PinDataLt<'_, '2>` + | + = note: requirement occurs because of the type `__PinDataLt<'_, '_>`, which makes the generic argument `'_` invariant + = note: the struct `__PinDataLt<'owner, 'later_owner>` is invariant over the parameter `'owner` + = help: see for more information about variance + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) From 2238c9d0fd5000b88d31aa5817ee5d001fe4242b Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 1 Oct 2026 14:37:21 +0100 Subject: [PATCH 11/20] internal: pin_data: allow lifetime to be shortened per field drop order 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. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 82 +++++++++---------- tests/selfref_shorten.rs | 30 +++++++ .../selfref_lifetime_specialize.stderr | 16 ---- 3 files changed, 70 insertions(+), 58 deletions(-) create mode 100644 tests/selfref_shorten.rs diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ad98bb9f..f27dff95 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -107,6 +107,7 @@ impl Ord for Capture { } } +#[expect(unused)] struct FieldInfo { field: Field, member: Member, @@ -124,8 +125,8 @@ struct StructInfo { field_idx_map: BTreeMap, 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 = ¶m.lifetime; + field_lts_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + GenericParam::Type(param) => { + let ty = ¶m.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) = @@ -842,10 +837,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(); @@ -1176,8 +1174,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(); diff --git a/tests/selfref_shorten.rs b/tests/selfref_shorten.rs new file mode 100644 index 00000000..3d12033c --- /dev/null +++ b/tests/selfref_shorten.rs @@ -0,0 +1,30 @@ +use std::marker::PhantomData; + +use pin_init::*; + +#[pin_data] +struct SelfRef { + phantom: PhantomData<&'bar ()>, + part: &'foo str, + foo: String, + bar: String, +} + +#[test] +fn self_ref() { + stack_pin_init!(let _foo = pin_init!(SelfRef { + phantom: PhantomData, + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + part: &foo[..5], + })); + + stack_pin_init!(let _bar = pin_init!(SelfRef { + phantom: PhantomData, + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + // In this case, we borrow from a field that lives longers. + // We're allowed to coerce it into a shorter-living lifetime. + part: &bar[..5], + })); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr index ced33018..886bd8d3 100644 --- a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr @@ -1,19 +1,3 @@ -error: lifetime may not live long enough - --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:24:15 - | -22 | #[pin_data] - | ----------- in this attribute macro expansion -23 | struct Bar { -24 | lt_spec2: LtSpec2<'a, 'b>, - | ^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a` -25 | a: u32, - | - lifetime `'a` defined here -26 | b: u32, - | - lifetime `'b` defined here - | - = help: consider adding the following bound: `'b: 'a` - = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) - error: higher-ranked lifetime error --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:34:5 | From 7b76ee2f2aa840985b63850b36765509800be162 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Sep 2026 13:30:18 +0100 Subject: [PATCH 12/20] internal: pin_data: parse explicit `#[borrowed]` annotation `#[borrowed]` attribute explicitly marks a field as potentially being borrowed by other fields. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index f27dff95..9b584e4c 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -11,8 +11,8 @@ use syn::{ 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 @@ impl ToTokens for Args { /// 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) -> Option { + 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, From ebf2b0ae6a50bf8f598b061150a9f518d02f3cb0 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 6 May 2026 18:12:59 +0100 Subject: [PATCH 13/20] internal: pin_data: support mutable borrows 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 --- examples/selfref.rs | 13 +++ internal/src/pin_data.rs | 94 +++++++++++++++++-- src/__internal.rs | 66 ++++++++++--- .../pin_data/selfref_mut_borrow.rs | 28 ++++++ .../pin_data/selfref_mut_borrow.stderr | 14 +++ .../pin_data/selfref_mut_borrowck.rs | 21 +++++ .../pin_data/selfref_mut_borrowck.stderr | 24 +++++ .../pin_data/selfref_project_mut.rs | 29 ++++++ .../pin_data/selfref_project_mut.stderr | 11 +++ .../pin_data/selfref_with_project.rs | 28 ++++++ .../pin_data/selfref_with_project.stderr | 15 +++ 11 files changed, 319 insertions(+), 24 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_project_mut.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_project_mut.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_with_project.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_with_project.stderr diff --git a/examples/selfref.rs b/examples/selfref.rs index b5cdf96b..5e9494dc 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -8,12 +8,18 @@ use pin_init::*; 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/internal/src/pin_data.rs b/internal/src/pin_data.rs index 9b584e4c..9910e14b 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -5,7 +5,7 @@ use std::collections::{BTreeMap, BTreeSet}; 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 @@ impl BorrowedKind { 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,)); @@ -916,9 +935,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>, @@ -986,7 +1014,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>, @@ -1106,6 +1152,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; @@ -1261,7 +1318,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/src/__internal.rs b/src/__internal.rs index d44a5b1f..c013d94a 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -361,6 +361,9 @@ impl Drop for DropGuard { } } +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 @@ impl Drop for DropGuard { /// - `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 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { /// 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 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'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(self, init: impl Init) -> Result, E> { + pub fn init(self, init: impl Init) -> Result, E> { // SAFETY: // - `self.ptr` is valid and properly aligned. // - when `Err` is returned, we also propagate the error without touching `ptr`; @@ -421,10 +424,13 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { } } -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(self, init: impl PinInit) -> Result, E> { + pub fn init( + self, + init: impl PinInit, + ) -> Result, E> { // SAFETY: // - `ptr` is valid // - when `Err` is returned, we also propagate the error without touching `ptr`; @@ -447,12 +453,12 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { /// - `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 @@ impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { } } -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 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, 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 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { } } -impl 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 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. diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs new file mode 100644 index 00000000..4f788fff --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +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], + })); + + // Should fail due to not accessible. + foo.as_mut().with_project(|proj| { + let _: &_ = proj.mut_str; + }) +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr new file mode 100644 index 00000000..4738074a --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs:22:21 + | +22 | let _: &_ = proj.mut_str; + | -- ^^^^^^^^^^^^ expected `&_`, found `NotVisible<&String>` + | | + | expected due to this + | + = note: expected reference `&_` + found struct `pin_init::__internal::NotVisible<&String>` +help: consider borrowing here + | +22 | let _: &_ = &proj.mut_str; + | + diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs new file mode 100644 index 00000000..d2cfa04a --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs @@ -0,0 +1,21 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + mut_part: &'str mut str, + #[borrowed(mut)] + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_part: &mut str[..5], + })); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr new file mode 100644 index 00000000..9d3748f9 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr @@ -0,0 +1,24 @@ +error[E0502]: cannot borrow value as mutable because it is also borrowed as immutable + --> tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs:15:24 + | +14 | part: &str[..5], + | ---- --- immutable borrow occurs here + | | + | immutable borrow later used here +15 | mut_part: &mut str[..5], + | ^^^ mutable borrow occurs here + +error[E0502]: cannot borrow value as mutable because it is also borrowed as immutable + --> tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs:15:24 + | +12 | stack_pin_init!(let foo = pin_init!(SelfRef { + | _______________________________- +13 | | str: "hello world".to_owned(), +14 | | part: &str[..5], + | | ---- --- immutable borrow occurs here + | | | + | | argument requires that immutable borrow lasts for `'1` +15 | | mut_part: &mut str[..5], + | | ^^^ mutable borrow occurs here +16 | | })); + | |______- has type `__PinDataLt<'1>` diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.rs b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs new file mode 100644 index 00000000..1df5df28 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs @@ -0,0 +1,29 @@ +use pin_init::*; + +#[pin_data] +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], + })); + + // Should fail due to reference not being mutable. + *foo.as_mut().project().part = "foo"; + + // Should fail due to reference not being mutable. + foo.as_mut().project().mut_part.make_ascii_uppercase(); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr new file mode 100644 index 00000000..57d88d84 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr @@ -0,0 +1,11 @@ +error[E0594]: cannot assign to data in a `&` reference + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:21:5 + | +21 | *foo.as_mut().project().part = "foo"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign + +error[E0596]: cannot borrow data in a `&` reference as mutable + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:24:5 + | +24 | foo.as_mut().project().mut_part.make_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.rs b/tests/ui/compile-fail/pin_data/selfref_with_project.rs new file mode 100644 index 00000000..f77690f4 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +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], + })); + + let local = "hello world".to_owned(); + foo.as_mut().with_project(|proj| { + *proj.part = &local; + }); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.stderr b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr new file mode 100644 index 00000000..ee1ed52b --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr @@ -0,0 +1,15 @@ +error[E0597]: `local` does not live long enough + --> tests/ui/compile-fail/pin_data/selfref_with_project.rs:22:23 + | +20 | let local = "hello world".to_owned(); + | ----- binding `local` declared here +21 | foo.as_mut().with_project(|proj| { + | ------ value captured here +22 | *proj.part = &local; + | --------------^^^^^ + | | | + | | borrowed value does not live long enough + | assignment requires that `local` is borrowed for `'static` +23 | }); +24 | } + | - `local` dropped here while still borrowed From d23c5bcc113501cdd439f5512853a70318fc2711 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Sep 2026 13:30:18 +0100 Subject: [PATCH 14/20] internal: pin_data: parse explicit `#[uses]` annotation 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. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 99 ++++++++++++++++- .../pin_data/selfref_invalid_attr.rs | 47 ++++++++ .../pin_data/selfref_invalid_attr.stderr | 101 ++++++++++++++++++ 3 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 9910e14b..ad43f0bb 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -21,6 +21,9 @@ use crate::{ }; 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 { + 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 @@ impl Ord for Capture { } } -#[expect(unused)] +impl Parse for Capture { + fn parse(input: ParseStream<'_>) -> syn::Result { + 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, + bound_lifetimes: &BTreeSet<&Lifetime>, + field_idx_map: &BTreeMap, + ) -> Option<(BTreeSet, Variance)> { + let attr = attrs.extract_single_attr(dcx, "uses")?; + let punctuated: Punctuated = 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(<.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(); diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs new file mode 100644 index 00000000..4ae5c8dc --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs @@ -0,0 +1,47 @@ +use pin_init::*; + +#[pin_data] +struct InvalidAttr<'a> { + #[uses('non_exist: covariant)] // Borrows non-existent fields + explicit: u32, + + implicit: &'non_exist u32, + + #[uses('b: covariant, 'b: invariant)] + duplicate: u32, + + #[borrowed] + valid_explicit: u32, + + #[borrowed(mut)] + valid_explicit_mut: u32, + + #[borrowed = "foobar"] + #[borrowed(foobar)] + invalid: u32, + + bound: &'a u32, + okay: &'b u32, + b: u32, +} + +#[pin_data] +struct Conflict<'a> { + b: &'a u32, + #[borrowed] + a: u32, +} + +#[pin_data] +struct InvalidTuple(#[uses(1)] u32, u32); + +#[pin_data] +struct InvalidBounds<'a: 'y> +where + 'a: 'x, +{ + y: &'x (), + x: &'a (), +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr new file mode 100644 index 00000000..97be8022 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -0,0 +1,101 @@ +error: `'non_exist` is not a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:5:12 + | +5 | #[uses('non_exist: covariant)] // Borrows non-existent fields + | ^^^^^^^^^^ + +error: `'non_exist` is neither a lifetime in generics nor a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ + +error: lifetime `'b` is mentioned more than once + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:27 + | +10 | #[uses('b: covariant, 'b: invariant)] + | ^^ + +error: `#[borrowed]` attribute specified more than once + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:20:5 + | +20 | #[borrowed(foobar)] + | ^^^^^^^^^^^^^^^^^^^ + +error: expected parentheses: #[borrowed(...)] + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:19:7 + | +19 | #[borrowed = "foobar"] + | ^^^^^^^^ + +error: `'a` appear in generics and would conflict with field lifetime + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:32:5 + | +32 | a: u32, + | ^ + +error: expected lifetime + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:36:28 + | +36 | struct InvalidTuple(#[uses(1)] u32, u32); + | ^ + +error: field lifetimes cannot be used in bounds + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:39:26 + | +39 | struct InvalidBounds<'a: 'y> + | ^^ + +error: field lifetimes cannot be used in bounds + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:41:9 + | +41 | 'a: 'x, + | ^^ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | +4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | +8 | implicit<'non_exist>: &'non_exist u32, + | ++++++++++++ +help: consider introducing lifetime `'non_exist` here + | +4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'y` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:39:26 + | +39 | struct InvalidBounds<'a: 'y> + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'y` here + | +39 | struct InvalidBounds<'y, 'a: 'y> + | +++ + +error[E0261]: use of undeclared lifetime name `'x` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:41:9 + | +41 | 'a: 'x, + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'x` here + | +39 | struct InvalidBounds<'x, 'a: 'y> + | +++ From 582ac96f6a3326f02ee465de6493db81460923da Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 9 Sep 2026 19:37:00 +0100 Subject: [PATCH 15/20] internal: pin_data: make field lifetime invariance imply type invariance 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, 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. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 175 +++++++++++++++++- src/__internal.rs | 8 +- .../pin_data/selfref_invariant_field_lt.rs | 29 +++ .../selfref_invariant_field_lt.stderr | 15 ++ .../selfref_invariant_field_lt_implied.rs | 47 +++++ .../selfref_invariant_field_lt_implied.stderr | 33 ++++ 6 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ad43f0bb..77c8ce26 100644 --- a/internal/src/pin_data.rs +++ b/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, 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) = @@ -1395,6 +1558,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`. @@ -1414,6 +1578,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/src/__internal.rs b/src/__internal.rs index c013d94a..9f0f5a34 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -676,7 +676,7 @@ where /// 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(PhantomPinned, T); +pub struct Borrowed(PhantomInvariant

, 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 @@ pub struct Borrowed(PhantomPinned, T); // 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 Drop for Borrowed { +impl Drop for Borrowed { #[inline(always)] fn drop(&mut self) {} } -impl Deref for Borrowed { +impl Deref for Borrowed { type Target = T; #[inline(always)] fn deref(&self) -> &T { - &self.1 + &self.2 } } diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs new file mode 100644 index 00000000..05e030ad --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs @@ -0,0 +1,29 @@ +#![allow(warnings)] + +use pin_init::*; +use std::{convert::Infallible, pin::Pin, sync::Mutex}; + +#[pin_data] +struct SelfRef<'a> { + #[uses('_: invariant)] + part: Mutex<&'outer str>, + + // In this case, the type of this field is covariant over `'a`. However, the field lifetime + // `'outer` is invariant. Conceptually, a field's type must outlive the field lifetime, so if we + // allow `'a` to be covariant, we can have a shortened `SelfRef<'short_a>` where `'outer` + // outlives `'short_a`. That will be unsound. + // + // Therefore, we need to ensure that all invariant field lifetimes will cause the field types + // themselves to also be invariant. + outer: &'a String, +} + +// Must fail. +fn shorten<'long: 'short, 'short>( + x: &'long SelfRef<'long>, + short: &'short String, +) -> &'short SelfRef<'short> { + x +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr new file mode 100644 index 00000000..416b7585 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs:26:5 + | +22 | fn shorten<'long: 'short, 'short>( + | ----- ------ lifetime `'short` defined here + | | + | lifetime `'long` defined here +... +26 | x + | ^ function was supposed to return data with lifetime `'long` but it is returning data with lifetime `'short` + | + = help: consider adding the following bound: `'short: 'long` + = note: requirement occurs because of the type `SelfRef<'_>`, which makes the generic argument `'_` invariant + = note: the struct `SelfRef<'a>` is invariant over the parameter `'a` + = help: see for more information about variance diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs new file mode 100644 index 00000000..0c8b799c --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs @@ -0,0 +1,47 @@ +use std::fmt::Display; +use std::marker::PhantomData; +use std::sync::Mutex; + +use pin_init::*; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct Foo<'a> { + // In this case, we will establish that `'a: 'early`, but `'early` is invariant, so we need to + // either make `'a` invariant (which is difficult because we cannot tell in proc macro), or reject + // such implied bounds. + marker: PhantomData<&'early &'later ()>, + #[uses('early: invariant)] + slot: Mutex>, + early: String, + later: &'a str, +} + +fn shorten<'long, 'short>(foo: &'short Foo<'long>) -> &'short Foo<'short> +where + 'long: 'short, +{ + foo +} + +fn main() { + stack_pin_init!(let foo = pin_init!(Foo { + early: "early".to_owned(), + later: "static", + slot: Mutex::new(PrintOnDrop("initial")), + marker: PhantomData, + })); + { + // let short = String::from("short"); + // shorten(&foo).with_project_ref(|proj| { + // *proj.slot.lock().unwrap() = PrintOnDrop(&short); + // }); + } +} diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr new file mode 100644 index 00000000..c541fc95 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr @@ -0,0 +1,33 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:20:13 + | +15 | #[pin_data] + | ----------- in this attribute macro expansion +... +20 | marker: PhantomData<&'early &'later ()>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'later` must outlive `'early` +... +23 | early: String, + | ----- lifetime `'early` defined here +24 | later: &'a str, + | ----- lifetime `'later` defined here + | + = help: consider adding the following bound: `'later: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:20:13 + | +15 | #[pin_data] + | ----------- in this attribute macro expansion +16 | struct Foo<'a> { + | -- lifetime `'a` defined here +... +20 | marker: PhantomData<&'early &'later ()>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'early` +... +23 | early: String, + | ----- lifetime `'early` defined here + | + = help: consider adding the following bound: `'a: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) From cc92c50187b1cd3ed15dc1e350bb64c13af6d072 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:40:15 +0100 Subject: [PATCH 16/20] internal: pin_data: complete invariant borrow support 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 --- examples/selfref.rs | 12 ++++++++++++ internal/src/pin_data.rs | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/examples/selfref.rs b/examples/selfref.rs index 5e9494dc..5212b925 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -6,6 +6,9 @@ use pin_init::*; #[pin_data] struct SelfRef { + #[uses('_: invariant)] + not_cov: Box 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/internal/src/pin_data.rs b/internal/src/pin_data.rs index 77c8ce26..4b361c51 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1441,7 +1441,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) }) + } + )) } } From 63cd2f7e00ce06a2d1611b5c936414150d68b68d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:58:33 +0100 Subject: [PATCH 17/20] internal: pin_data: perform AST lifetime replacement if possible 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. Signed-off-by: Gary Guo --- internal/src/util.rs | 112 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/internal/src/util.rs b/internal/src/util.rs index 59054f59..4d3331b8 100644 --- a/internal/src/util.rs +++ b/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 @@ impl GenericParamExt for GenericParam { } 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(<.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 } } From 3a36d0e80d92c40b23dbdb3449a8787a8e094a42 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 8 Sep 2026 23:54:51 +0100 Subject: [PATCH 18/20] internal: pin_data: support shared projection 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 --- internal/src/pin_data.rs | 134 ++++++++++++++++++++ tests/ui/compile-fail/pin_data/twice.stderr | 10 ++ tests/ui/expand/many_generics.expanded.rs | 38 ++++++ tests/ui/expand/pin-data.expanded.rs | 26 ++++ tests/ui/expand/pinned_drop.expanded.rs | 26 ++++ tests/ui/expand/tuple_struct.expanded.rs | 31 +++++ 6 files changed, 265 insertions(+) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 4b361c51..8ab71544 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1145,10 +1145,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"); @@ -1462,6 +1469,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 @@ -1477,6 +1587,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 { @@ -1515,6 +1631,24 @@ fn generate_projections(info: &StructInfo) -> TokenStream { 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)* } } diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index 4c56344a..9f538a56 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -37,3 +37,13 @@ error[E0592]: duplicate definitions with name `with_project` | ^^^^^^^^^^^ duplicate definitions for `with_project` | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `with_project_ref` + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 + | +3 | #[pin_data] + | ----------- other definition for `with_project_ref` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `with_project_ref` + | + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index acd6f229..e7fb2634 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -51,6 +51,23 @@ const _: () = { &'__this mut Foo<'a, 'b, T, SIZE>, >, } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef< + '__this, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__this [u8; 1024 * 1024], + r: &'__this &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo<'a, 'b, T, SIZE>>, + } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> where T: Bar<'a, 1>, @@ -96,6 +113,27 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, }) } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this, 'a, 'b, T, SIZE>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + r: &this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 5945bfc3..b821bad7 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -21,6 +21,13 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this> { + array: &'__this [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -59,6 +66,25 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, }) } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 6ad52bdf..c90ad117 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -21,6 +21,13 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this> { + array: &'__this [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -59,6 +66,25 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, }) } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] #[allow(non_snake_case)] diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index 0c96de92..4ae147e6 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -20,6 +20,14 @@ const _: () = { &'__this mut usize, ::core::marker::PhantomData<&'__this mut Foo<'a, T, N>>, ); + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this, 'a, T: Copy, const N: usize>( + &'__this &'a mut [T; N], + ::core::pin::Pin<&'__this PhantomPinned>, + &'__this usize, + ::core::marker::PhantomData<&'__this Foo<'a, T, N>>, + ); impl<'a, T: Copy, const N: usize> Foo<'a, T, N> { /// Pin-projects all fields of `Self`. /// @@ -64,6 +72,29 @@ const _: () = { ), ) } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this, 'a, T, N>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f( + __ProjectionRef( + &this.0, + unsafe { ::core::pin::Pin::new_unchecked(&this.1) }, + &this.2, + ::core::marker::PhantomData, + ), + ) + } } #[doc(hidden)] #[allow(non_snake_case)] From 315a8077a4f21911cda59296e3bda36f3d0a6e02 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 9 Sep 2026 23:23:20 +0100 Subject: [PATCH 19/20] internal: pin_data: support existential lifetimes 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 --- internal/src/pin_data.rs | 253 ++++++++++++++++++++++++++++++++++++--- src/__internal.rs | 15 +++ tests/existential.rs | 35 ++++++ 3 files changed, 287 insertions(+), 16 deletions(-) create mode 100644 tests/existential.rs diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 8ab71544..dbf9084b 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -11,8 +11,9 @@ use syn::{ 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 @@ impl Capture { dcx: &mut DiagCtxt, attrs: &mut Vec, bound_lifetimes: &BTreeSet<&Lifetime>, + exist_lifetimes: &BTreeSet, field_idx_map: &BTreeMap, ) -> Option<(BTreeSet, Variance)> { let attr = attrs.extract_single_attr(dcx, "uses")?; @@ -208,7 +210,10 @@ impl Capture { continue; } - if lt.ident != "_" && !field_idx_map.contains_key(<.ident) { + if lt.ident != "_" + && !exist_lifetimes.contains(lt) + && !field_idx_map.contains_key(<.ident) + { dcx.error(lt, format!("`{lt}` is not a field name")); continue; } @@ -240,11 +245,15 @@ struct StructInfo { field_idx_map: BTreeMap, is_tuple_struct: bool, self_referential: bool, + /// Existential lifetimes defined. + exist_lts: BTreeSet, /// 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(<) { + 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(<.ident) { + if !exist_lts.contains(lt) && !field_idx_map.contains_key(<.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, +} + +impl std::borrow::Borrow 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 { + 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 { + fn parse_one( + dcx: &mut DiagCtxt, + pred: &WherePredicate, + result: &mut BTreeSet, + ) -> 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) = @@ -1138,20 +1342,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, _) = @@ -1665,10 +1874,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(); @@ -1778,6 +1996,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { }) .collect::(); + 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)] @@ -1833,7 +2052,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { { /// 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, @@ -1844,7 +2063,9 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { } #[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/src/__internal.rs b/src/__internal.rs index 9f0f5a34..04abb759 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -725,3 +725,18 @@ impl NotVisible { 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(PhantomData

, T); diff --git a/tests/existential.rs b/tests/existential.rs new file mode 100644 index 00000000..bf709ad3 --- /dev/null +++ b/tests/existential.rs @@ -0,0 +1,35 @@ +#![allow(dead_code)] + +use std::marker::PhantomData; +use std::sync::Mutex; + +use pin_init::*; + +// An example kernel use case, where `'a` is the lifetime of device, and `T` being some other owned +// data. +struct CoherentBox<'a, T>(PhantomData<(&'a (), T)>); + +#[pin_data] +struct Foo<'a> +where + exists<'x>: 'a, +{ + // We have a mutex to synchronize the access to the data. + // But we never want to put in a `CoherentBox` from another device, + // so ideally we want this data structure to be covariant over `'a`. + #[uses('x: invariant)] + m: Mutex>, + p: PhantomData<&'a ()>, +} + +fn shorten<'long: 'short, 'short>(f: Foo<'long>) -> Foo<'short> { + f +} + +fn use_foo(f: &Foo) { + f.with_m(|m| { + let mut g = m.lock().unwrap(); + /* do something with `g` */ + let _ = &mut *g; + }) +} From bb0c889a61ad45160dec3eec6b3a9ecb7473ccc5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 29 Sep 2026 15:00:24 +0100 Subject: [PATCH 20/20] tests: add self reference test suites These are test suites that are gathered when developing pin-init self-reference and is a collection of unsoundness issues discovered along the process. Signed-off-by: Gary Guo --- tests/selfref_shorten.rs | 124 +++++++++++++++++- .../init/selfref_field_selfref.rs | 30 +++++ .../init/selfref_field_selfref.stderr | 5 + .../compile-fail/init/selfref_may_dangle.rs | 18 +++ .../init/selfref_may_dangle.stderr | 13 +- .../init/selfref_mutate_in_init.rs | 60 +++++++++ .../init/selfref_mutate_in_init.stderr | 26 ++++ .../pin_data/selfref_always_pin.rs | 9 +- .../pin_data/selfref_always_pin.stderr | 40 ++++-- .../selfref_invariant_field_lt_implied.rs | 21 +++ .../selfref_invariant_field_lt_implied.stderr | 33 +++++ 11 files changed, 360 insertions(+), 19 deletions(-) create mode 100644 tests/ui/compile-fail/init/selfref_field_selfref.rs create mode 100644 tests/ui/compile-fail/init/selfref_field_selfref.stderr create mode 100644 tests/ui/compile-fail/init/selfref_mutate_in_init.rs create mode 100644 tests/ui/compile-fail/init/selfref_mutate_in_init.stderr diff --git a/tests/selfref_shorten.rs b/tests/selfref_shorten.rs index 3d12033c..fbe83a10 100644 --- a/tests/selfref_shorten.rs +++ b/tests/selfref_shorten.rs @@ -1,10 +1,11 @@ use std::marker::PhantomData; +use std::sync::Mutex; use pin_init::*; #[pin_data] struct SelfRef { - phantom: PhantomData<&'bar ()>, + #[uses('bar)] part: &'foo str, foo: String, bar: String, @@ -13,14 +14,12 @@ struct SelfRef { #[test] fn self_ref() { stack_pin_init!(let _foo = pin_init!(SelfRef { - phantom: PhantomData, foo: "hello world".to_owned(), bar: "hello world".to_owned(), part: &foo[..5], })); stack_pin_init!(let _bar = pin_init!(SelfRef { - phantom: PhantomData, foo: "hello world".to_owned(), bar: "hello world".to_owned(), // In this case, we borrow from a field that lives longers. @@ -28,3 +27,122 @@ fn self_ref() { part: &bar[..5], })); } + +// This struct is covariant over `'a`, despite there are some invariant fields. +#[pin_data] +struct SelfRefCov<'a> { + cov_part: PhantomData<&'cov_owner ()>, + cov_owner: &'a String, + #[uses('owner: invariant)] + part: Mutex<&'owner str>, + owner: String, +} + +// Swapping the order of unrelated groups should not affect the variance. +#[pin_data] +struct SelfRefCovSwapped<'a> { + #[uses('owner: invariant)] + part: Mutex<&'owner str>, + owner: String, + cov_part: PhantomData<&'cov_owner ()>, + cov_owner: &'a String, +} + +fn shorten_cov<'long, 'short>(foo: &'short SelfRefCov<'long>) -> &'short SelfRefCov<'short> +where + 'long: 'short, +{ + foo +} + +fn shorten_cov_swapped<'long, 'short>( + foo: &'short SelfRefCovSwapped<'long>, +) -> &'short SelfRefCovSwapped<'short> +where + 'long: 'short, +{ + foo +} + +fn init_cov(s: &String) -> impl PinInit> + '_ { + pin_init!(SelfRefCov { + cov_part: PhantomData, + cov_owner: s, + owner: "early".to_owned(), + part: Mutex::new("static"), + }) +} + +fn init_cov_swapped(s: &String) -> impl PinInit> + '_ { + pin_init!(SelfRefCovSwapped { + owner: "early".to_owned(), + part: Mutex::new("static"), + cov_part: PhantomData, + cov_owner: s, + }) +} + +#[test] +fn shorten_with_inv_field() { + let s = "hello".to_owned(); + stack_pin_init!(let first = init_cov(&s)); + let _first: &SelfRefCov<'_> = shorten_cov(&first); + stack_pin_init!(let second = init_cov_swapped(&s)); + let _second: &SelfRefCovSwapped<'_> = shorten_cov_swapped(&second); +} + +// Shortening is allowed, even for invariant field. +// +// This capability is only allowed in `with_project` though (`with_project_ref` rejects this). +#[pin_data] +struct SelfRefInv { + #[uses('early: invariant, 'later)] + part: Mutex<&'early str>, + early: String, + later: String, +} + +#[test] +fn shorten_inv_with_project() { + stack_pin_init!(let foo = pin_init!(SelfRefInv { + early: "early".to_owned(), + later: "later".to_owned(), + part: Mutex::new("static"), + })); + foo.as_mut().with_project(|proj| { + *proj.part.get_mut().unwrap() = proj.later.as_str(); + }); + assert_eq!( + foo.as_ref() + .with_project_ref(|proj| *proj.part.lock().unwrap()), + "later" + ); +} + +// Mutation in initializer is okay, as long as the order is correct. +// See tests/ui/compile-fail/selfref_mutate_in_init.rs for a wrong example. +#[pin_data] +struct MutateInInit { + #[uses('early: invariant, 'later)] + part: Mutex<&'early str>, + early: String, + later: String, +} + +#[test] +fn mutate_in_init() { + stack_try_pin_init!(let foo = pin_init!(MutateInInit { + early: "early".to_owned(), + later: "later".to_owned(), + part: Mutex::new(""), + _: { + *part.get_mut().unwrap() = later; + }, + }? ())); + let foo = foo.unwrap(); + assert_eq!( + foo.as_ref() + .with_project_ref(|proj| *proj.part.lock().unwrap()), + "later" + ); +} diff --git a/tests/ui/compile-fail/init/selfref_field_selfref.rs b/tests/ui/compile-fail/init/selfref_field_selfref.rs new file mode 100644 index 00000000..e7155e25 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_field_selfref.rs @@ -0,0 +1,30 @@ +use pin_init::*; +use std::fmt::Display; +use std::sync::Mutex; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct SelfRef { + // Given the drop glue, this will essentially imply that `'r` strictly outlive `'r` which is + // obviously nonsense. + #[uses('_: invariant)] + r: (String, Mutex>), +} + +fn main() { + let mut data = Box::pin_init(pin_init!(SelfRef { + r: ("hello".to_owned(), Mutex::new(PrintOnDrop("str"))), + })) + .unwrap(); + + data.as_mut().with_project(|data| { + *data.r.1.lock().unwrap() = PrintOnDrop(&data.r.0); + }) +} diff --git a/tests/ui/compile-fail/init/selfref_field_selfref.stderr b/tests/ui/compile-fail/init/selfref_field_selfref.stderr new file mode 100644 index 00000000..53f9ff94 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_field_selfref.stderr @@ -0,0 +1,5 @@ +error: field `r` cannot borrow from itself + --> tests/ui/compile-fail/init/selfref_field_selfref.rs:18:36 + | +18 | r: (String, Mutex>), + | ^^ diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.rs b/tests/ui/compile-fail/init/selfref_may_dangle.rs index 48654b2c..0223b69f 100644 --- a/tests/ui/compile-fail/init/selfref_may_dangle.rs +++ b/tests/ui/compile-fail/init/selfref_may_dangle.rs @@ -16,6 +16,16 @@ struct SelfRef<'a> { outer: &'a String, } +// Similar one to the above, but make use of `exists`. +#[pin_data] +struct ExistsOwner<'a> +where + exists<'x>: 'a, +{ + part: PrintOnDrop<&'outer String>, + outer: &'x String, +} + fn new<'a>(str: &'a String) -> impl PinInit, Infallible> { pin_init!(SelfRef { outer: str, @@ -23,8 +33,16 @@ fn new<'a>(str: &'a String) -> impl PinInit, Infallible> { }) } +fn new_exists<'a>(s: &'a String) -> impl PinInit, Infallible> { + pin_init!(ExistsOwner { + outer: s, + part: PrintOnDrop(*outer), + }) +} + fn main() { let str = "hello world".to_owned(); let _selfref = Box::pin_init(new(&str)).unwrap(); + let _selfref_exists = Box::pin_init(new_exists(&str)).unwrap(); drop(str); } diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.stderr b/tests/ui/compile-fail/init/selfref_may_dangle.stderr index 60d35aa0..de18691c 100644 --- a/tests/ui/compile-fail/init/selfref_may_dangle.stderr +++ b/tests/ui/compile-fail/init/selfref_may_dangle.stderr @@ -1,16 +1,17 @@ error[E0505]: cannot move out of `str` because it is borrowed - --> tests/ui/compile-fail/init/selfref_may_dangle.rs:29:10 + --> tests/ui/compile-fail/init/selfref_may_dangle.rs:47:10 | -27 | let str = "hello world".to_owned(); +44 | let str = "hello world".to_owned(); | --- binding `str` declared here -28 | let _selfref = Box::pin_init(new(&str)).unwrap(); +45 | let _selfref = Box::pin_init(new(&str)).unwrap(); | ---- borrow of `str` occurs here -29 | drop(str); +46 | let _selfref_exists = Box::pin_init(new_exists(&str)).unwrap(); +47 | drop(str); | ^^^ move out of `str` occurs here -30 | } +48 | } | - borrow might be used here, when `_selfref` is dropped and runs the destructor for type `Pin>>` | help: consider cloning the value if the performance cost is acceptable | -28 | let _selfref = Box::pin_init(new(&str.clone())).unwrap(); +45 | let _selfref = Box::pin_init(new(&str.clone())).unwrap(); | ++++++++ diff --git a/tests/ui/compile-fail/init/selfref_mutate_in_init.rs b/tests/ui/compile-fail/init/selfref_mutate_in_init.rs new file mode 100644 index 00000000..391faf55 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_mutate_in_init.rs @@ -0,0 +1,60 @@ +#![allow(warnings)] +use pin_init::*; +use std::fmt::Display; +use std::marker::PhantomData; +use std::sync::Mutex; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct Direct { + #[uses('early: invariant)] + part: Mutex>, + early: String, +} + +#[pin_data] +struct Indirect { + #[uses('early: invariant, 'later)] + part: Mutex>, + early: String, + later: String, +} + +fn broken_direct() -> impl PinInit { + pin_init!(Direct { + part: Mutex::new(PrintOnDrop("")), + early: "hello world".to_owned(), + _: { + *part.lock().unwrap() = PrintOnDrop(early); + if true { + return Err(()); + } + }, + }? ()) +} + +fn broken_indirect() -> impl PinInit { + pin_init!(Indirect { + part: Mutex::new(PrintOnDrop("")), + later: "later".to_owned(), + early: "early".to_owned(), + _: { + *part.lock().unwrap() = PrintOnDrop(early); + if true { + return Err(()); + } + }, + }? ()) +} + +fn main() { + stack_try_pin_init!(let _s = broken_direct()); + stack_try_pin_init!(let _s = broken_indirect()); +} diff --git a/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr b/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr new file mode 100644 index 00000000..65a75b60 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr @@ -0,0 +1,26 @@ +error[E0505]: cannot move out of value because it is borrowed + --> tests/ui/compile-fail/init/selfref_mutate_in_init.rs:33:9 + | +32 | part: Mutex::new(PrintOnDrop("")), + | ---- borrow later used here +33 | early: "hello world".to_owned(), + | ^^^^^ + | | + | move out of value occurs here + | borrow of value occurs here + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0505]: cannot move out of value because it is borrowed + --> tests/ui/compile-fail/init/selfref_mutate_in_init.rs:47:9 + | +45 | part: Mutex::new(PrintOnDrop("")), + | ---- borrow later used here +46 | later: "later".to_owned(), +47 | early: "early".to_owned(), + | ^^^^^ + | | + | move out of value occurs here + | borrow of value occurs here + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.rs b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs index 7abef5aa..b109dfee 100644 --- a/tests/ui/compile-fail/pin_data/selfref_always_pin.rs +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs @@ -2,6 +2,12 @@ use pin_init::*; +#[pin_data] +struct Foo { + #[borrowed] + f: u32, +} + #[pin_data] struct Bar { b: &'f u32, @@ -10,7 +16,7 @@ struct Bar { #[pin_data] struct Baz { - b: &'f u32, + #[borrowed] f: u32, } @@ -21,5 +27,6 @@ fn assert_unpin() {} fn main() { // All of the below checks must fail. + assert_unpin::(); assert_unpin::(); } diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr index 0b6f4462..a64c174c 100644 --- a/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr @@ -1,33 +1,55 @@ error[E0119]: conflicting implementations of trait `Unpin` for type `Baz` - --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:11:1 + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:17:1 | -11 | #[pin_data] +17 | #[pin_data] | ^^^^^^^^^^^ conflicting implementation for `Baz` ... -18 | impl Unpin for Baz {} +24 | impl Unpin for Baz {} | ------------------ first implementation here | = note: upstream crates may add a new impl of trait `std::marker::Unpin` for type `std::marker::PhantomPinned` in future versions = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `PhantomPinned` cannot be unpinned - --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:24:20 + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:30:20 | -24 | assert_unpin::(); +30 | assert_unpin::(); | ^^^ the trait `Unpin` is not implemented for `PhantomPinned` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope -note: required for `Bar` to implement `Unpin` +note: required for `Foo` to implement `Unpin` --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:5:1 | 5 | #[pin_data] | ^^^^^^^^^^^ unsatisfied trait bound introduced here - 6 | struct Bar { + 6 | struct Foo { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:26:20 + | +26 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:31:20 + | +31 | assert_unpin::(); + | ^^^ the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required for `Bar` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:11:1 + | +11 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here +12 | struct Bar { | ^^^ note: required by a bound in `assert_unpin` - --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:20:20 + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:26:20 | -20 | fn assert_unpin() {} +26 | fn assert_unpin() {} | ^^^^^ required by this bound in `assert_unpin` = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs index 0c8b799c..0b7d6fd6 100644 --- a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs @@ -31,6 +31,27 @@ where foo } +// Rejected by split variance chain. Could technically be allowed. +// `&'early &'later ()` is well-formed only when `'later` outlives the invariant `'early`. +#[pin_data] +struct SplitVarianceChain { + link: &'early &'later (), + #[uses('early: invariant)] + slot: Mutex<&'early str>, + early: String, + later: String, +} + +// Rejected by split variance chain. Must not be allowed. +// `&'early &'a ()` is well-formed only when `'a` outlives the invariant `'early`. +#[pin_data] +struct SplitVarianceChainWithGeneric<'a> { + link: &'early &'a (), + #[uses('early: invariant)] + slot: Mutex<&'early str>, + early: String, +} + fn main() { stack_pin_init!(let foo = pin_init!(Foo { early: "early".to_owned(), diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr index c541fc95..1794be4b 100644 --- a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr @@ -31,3 +31,36 @@ error: lifetime may not live long enough | = help: consider adding the following bound: `'a: 'early` = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:38:11 + | +36 | #[pin_data] + | ----------- in this attribute macro expansion +37 | struct SplitVarianceChain { +38 | link: &'early &'later (), + | ^^^^^^^^^^^^^^^^^^ requires that `'later` must outlive `'early` +... +41 | early: String, + | ----- lifetime `'early` defined here +42 | later: String, + | ----- lifetime `'later` defined here + | + = help: consider adding the following bound: `'later: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:49:11 + | +47 | #[pin_data] + | ----------- in this attribute macro expansion +48 | struct SplitVarianceChainWithGeneric<'a> { + | -- lifetime `'a` defined here +49 | link: &'early &'a (), + | ^^^^^^^^^^^^^^ requires that `'a` must outlive `'early` +... +52 | early: String, + | ----- lifetime `'early` defined here + | + = help: consider adding the following bound: `'a: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)