diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index cf6142cd656d..36678843251c 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -417,6 +417,22 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { ty, } = &field.field; + let mut ty = ty.to_token_stream(); + + // Replace lifetime for self-referential fields. + if !field.captures.is_empty() { + // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt` + // implementation and thus may be used inside `Erase`. + ty = quote!((#ty,)); + + for borrow in field.captures.iter().rev() { + let lt = &borrow.lifetime; + ty = quote!(for<#lt> fn(&#lt()) -> #ty); + } + + ty = quote!(::pin_init::__internal::Erase<#ty>); + }; + quote! { #(#attrs)* #vis #ident #colon_token #ty } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index cba53b8c3c94..0a8247473cbc 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -385,3 +385,80 @@ unsafe fn __init(self, _slot: *mut T) -> Result<(), ()> { Err(()) } } +/// Polyfill of `FnOnce` trait to be able to reference output via associated type. +pub trait FnOutput { + 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, +{ +}