From: Gary Guo <gary@garyguo.net>
To: "Benno Lossin" <lossin@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
Gary Guo <gary@garyguo.net>
Subject: [PATCH 7/8] rust: pin-init: internal: project slots instead of references
Date: Tue, 12 May 2026 13:09:52 +0100 [thread overview]
Message-ID: <20260512-pin-init-sync-v1-7-81963130dfbd@garyguo.net> (raw)
In-Reply-To: <20260512-pin-init-sync-v1-0-81963130dfbd@garyguo.net>
By projecting slots, the `pin_init!` and `init!` code path can be more
unified. This also reduces the amount of macro-generated code and shifts
them to the shared infrastructure.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/init.rs | 113 +++++++++++----------------------
rust/pin-init/internal/src/pin_data.rs | 52 ++++-----------
rust/pin-init/src/__internal.rs | 77 ++++++++++++++++++++++
rust/pin-init/src/lib.rs | 12 ++--
4 files changed, 132 insertions(+), 122 deletions(-)
diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index 11affa76d1fc..e6f5ea06f91b 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use proc_macro2::{Span, TokenStream};
-use quote::{format_ident, quote, quote_spanned};
+use quote::{format_ident, quote};
use syn::{
braced,
parse::{End, Parse},
@@ -242,102 +242,61 @@ fn init_fields(
}
};
- let init = match kind {
- InitializerKind::Value { ident, value } => {
- let mut value_ident = ident.clone();
- let value_prep = value.as_ref().map(|value| &value.1).map(|value| {
- // Setting the span of `value_ident` to `value`'s span improves error messages
- // when the type of `value` is wrong.
- value_ident.set_span(value.span());
- quote!(let #value_ident = #value;)
- });
- // Again span for better diagnostics
- let write = quote_spanned!(ident.span()=> ::core::ptr::write);
- quote! {
- #(#attrs)*
- {
- #value_prep
- // SAFETY: TODO
- unsafe { #write(&raw mut (*#slot).#ident, #value_ident) };
- }
- }
- }
- InitializerKind::Init { ident, value, .. } => {
- // Again span for better diagnostics
- let init = format_ident!("init", span = value.span());
- let value_init = if pinned {
- quote! {
- // SAFETY:
- // - `slot` is valid, because we are inside of an initializer closure, we
- // return when an error/panic occurs.
- // - We also use `#data` to require the correct trait (`Init` or `PinInit`)
- // for `#ident`.
- unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
- }
- } else {
- quote! {
- // SAFETY: `slot` is valid, because we are inside of an initializer
- // closure, we return when an error/panic occurs.
- unsafe {
- ::pin_init::Init::__init(
- #init,
- &raw mut (*#slot).#ident,
- )?
- };
- }
- };
- quote! {
- #(#attrs)*
- {
- let #init = #value;
- #value_init
- }
- }
- }
- InitializerKind::Code { .. } => unreachable!(),
- };
-
- // `mixed_site` ensures that the guard is not accessible to the user-controlled code.
- let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
-
- let guard_creation = if pinned {
- let project_ident = format_ident!("__project_{ident}");
+ let slot = if pinned {
quote! {
// SAFETY:
// - `&raw mut (*slot).#ident` points to the `#ident` field of `slot`.
// - `&raw mut (*slot).#ident` is valid.
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
- // - `(*slot).#ident` has been initialized above.
- // - We only need the ownership to the pointee back when initialization has
- // succeeded, where we `forget` the guard.
- unsafe { #data.#project_ident(&raw mut (*slot).#ident) }
+ // - `make_field_check` prevents `#ident` from being used twice, therefore
+ // `(*slot).#ident` is exclusively accessed and has not been initialized.
+ (unsafe { #data.#ident(&raw mut (*#slot).#ident) })
}
} else {
quote! {
+ // For `init!()` macro, everything is unpinned.
// SAFETY:
// - `&raw mut (*slot).#ident` is valid.
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
- // - `(*slot).#ident` has been initialized above.
- // - We only need the ownership to the pointee back when initialization has
- // succeeded, where we `forget` the guard.
- unsafe {
- ::pin_init::__internal::DropGuard::<::pin_init::__internal::Unpinned, _>::new(
- &raw mut (*slot).#ident
+ // - `make_field_check` prevents `#ident` from being used twice, therefore
+ // `(*slot).#ident` is exclusively accessed and has not been initialized.
+ (unsafe {
+ ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new(
+ &raw mut (*#slot).#ident
)
+ })
+ }
+ };
+
+ // `mixed_site` ensures that the guard is not accessible to the user-controlled code.
+ let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
+
+ let init = match kind {
+ InitializerKind::Value { ident, value } => {
+ let value = value
+ .as_ref()
+ .map(|(_, value)| quote!(#value))
+ .unwrap_or_else(|| quote!(#ident));
+
+ quote! {
+ #(#attrs)*
+ let mut #guard = #slot.write(#value);
+
}
}
+ InitializerKind::Init { value, .. } => {
+ quote! {
+ #(#attrs)*
+ let mut #guard = #slot.init(#value)?;
+ }
+ }
+ InitializerKind::Code { .. } => unreachable!(),
};
res.extend(quote! {
#init
#(#cfgs)*
- let mut #guard = #guard_creation;
-
- #(#cfgs)*
- // NOTE: The reference is derived from the guard so that it only lives as long as the
- // guard does and cannot escape the scope. If it's created via `&mut (*#slot).#ident`
- // like the unaligned field guard, it will become effectively `'static`.
#[allow(unused_variables)]
let #ident = #guard.let_binding();
});
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 713a43c27826..3278a54510e1 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -352,10 +352,9 @@ fn generate_the_pin_data(
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
// For every field, we create an initializing projection function according to its projection
- // type. If a field is structurally pinned, then it must be initialized via `PinInit`, if it is
- // not structurally pinned, then it can be initialized via `Init`.
- //
- // The functions are `unsafe` to prevent accidentally calling them.
+ // 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 = fields
.iter()
.map(|f| {
@@ -370,54 +369,29 @@ fn generate_the_pin_data(
let field_name = ident
.as_ref()
.expect("only structs with named fields are supported");
- let project_ident = format_ident!("__project_{field_name}");
- let (init_ty, init_fn, pin_marker, pin_safety) = if f.pinned {
- (
- quote!(PinInit),
- quote!(__pinned_init),
- quote!(Pinned),
- quote!(
- /// - `slot` will not move until it is dropped, i.e. it will be pinned.
- ),
- )
+ let pin_marker = if f.pinned {
+ quote!(Pinned)
} else {
- (quote!(Init), quote!(__init), quote!(Unpinned), quote!())
+ quote!(Unpinned)
};
quote! {
- /// # Safety
- ///
- /// - `slot` is a valid pointer to uninitialized memory.
- /// - the caller does not touch `slot` when `Err` is returned, they are only
- /// permitted to deallocate.
- #pin_safety
- #(#attrs)*
- #vis unsafe fn #field_name<E>(
- self,
- slot: *mut #ty,
- init: impl ::pin_init::#init_ty<#ty, E>,
- ) -> ::core::result::Result<(), E> {
- // SAFETY: this function has the same safety requirements as the __init function
- // called below.
- unsafe { ::pin_init::#init_ty::#init_fn(init, slot) }
- }
-
/// # Safety
///
/// - `slot` points to a `#ident` field of a pinned struct that this `__ThePinData`
- /// describes.
- /// - `slot` is valid and properly aligned.
- /// - `*slot` is initialized, and the ownership is transferred to the returned
- /// guard.
+ /// describes.
+ /// - `slot` is a valid, properly aligned and points to uninitialized and
+ /// exclusively accessed memory.
#(#attrs)*
- #vis unsafe fn #project_ident(
+ #[inline(always)]
+ #vis unsafe fn #field_name(
self,
slot: *mut #ty,
- ) -> ::pin_init::__internal::DropGuard<::pin_init::__internal::#pin_marker, #ty> {
+ ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> {
// SAFETY:
// - If `#pin_marker` is `Pinned`, the corresponding field is structurally
// pinned.
// - Other safety requirements follows the safety requirement.
- unsafe { ::pin_init::__internal::DropGuard::new(slot) }
+ unsafe { ::pin_init::__internal::Slot::new(slot) }
}
}
})
diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs
index d7fdcfef41d2..854fbcaa93f3 100644
--- a/rust/pin-init/src/__internal.rs
+++ b/rust/pin-init/src/__internal.rs
@@ -251,6 +251,83 @@ struct Foo {
pub struct Pinned;
pub struct Unpinned;
+/// Represent an uninitialized field.
+///
+/// # Invariants
+///
+/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory.
+/// - If `P` is `Pinned`, then `ptr` is structurally pinned.
+pub struct Slot<P, T: ?Sized> {
+ ptr: *mut T,
+ _phantom: PhantomData<P>,
+}
+
+impl<P, T: ?Sized> Slot<P, T> {
+ /// # Safety
+ ///
+ /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed
+ /// memory.
+ /// - If `P` is `Pinned`, then `ptr` is structurally pinned.
+ #[inline(always)]
+ pub unsafe fn new(ptr: *mut T) -> Self {
+ // INVARIANT: Per safety requirement.
+ Self {
+ ptr,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Initialize the field by value.
+ #[inline(always)]
+ pub fn write(self, value: T) -> DropGuard<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 and properly aligned 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 { DropGuard::new(self.ptr) }
+ }
+}
+
+impl<T: ?Sized> Slot<Unpinned, T> {
+ /// Initialize the field.
+ #[inline(always)]
+ pub fn init<E>(self, init: impl Init<T, E>) -> Result<DropGuard<Unpinned, T>, E> {
+ // SAFETY:
+ // - `self.ptr` is valid and properly aligned.
+ // - when `Err` is returned, we also propagate the error without touching `slot`;
+ // also `self` is consumed so it cannot be touched further.
+ unsafe { init.__init(self.ptr)? };
+
+ // SAFETY:
+ // - `self.ptr` is valid and properly aligned per type invariant.
+ // - `*self.ptr` is initialized above and the ownership is transferred to the guard.
+ Ok(unsafe { DropGuard::new(self.ptr) })
+ }
+}
+
+impl<T: ?Sized> Slot<Pinned, T> {
+ /// Initialize the field.
+ #[inline(always)]
+ pub fn init<E>(self, init: impl PinInit<T, E>) -> Result<DropGuard<Pinned, T>, 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.
+ // - the drop guard will not hand out `&mut` (only `Pin<&mut T>`).
+ unsafe { init.__pinned_init(self.ptr)? };
+
+ // SAFETY:
+ // - `self.ptr` is valid, properly aligned and pinned per type invariant.
+ // - `*self.ptr` is initialized above and the ownership is transferred to the guard.
+ Ok(unsafe { DropGuard::new(self.ptr) })
+ }
+}
+
/// When a value of this type is dropped, it drops a `T`.
///
/// Can be forgotten to prevent the drop.
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index 4098c65d63c3..e891d65cc469 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -867,12 +867,12 @@ macro_rules! stack_try_pin_init {
#[macro_export]
macro_rules! assert_pinned {
($ty:ty, $field:ident, $field_ty:ty, inline) => {
- let _ = move |ptr: *mut $field_ty| {
- // SAFETY: This code is unreachable.
- let data = unsafe { <$ty as $crate::__internal::HasPinData>::__pin_data() };
- let init = $crate::__internal::AlwaysFail::<$field_ty>::new();
- // SAFETY: This code is unreachable.
- unsafe { data.$field(ptr, init) }.ok();
+ // SAFETY: This code is unreachable.
+ let _ = move |ptr: *mut $field_ty| unsafe {
+ let data = <$ty as $crate::__internal::HasPinData>::__pin_data();
+ _ = data
+ .$field(ptr)
+ .init($crate::__internal::AlwaysFail::<$field_ty>::new());
};
};
--
2.51.2
next prev parent reply other threads:[~2026-05-12 12:10 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-12 12:09 [PATCH 0/8] rust: pin-init: internal refactors Gary Guo
2026-05-12 12:09 ` [PATCH 1/8] rust: pin-init: internal: pin_data: use closure for `handle_field` Gary Guo
2026-05-12 12:09 ` [PATCH 2/8] rust: pin-init: internal: pin_data: add struct to record field info Gary Guo
2026-05-12 12:09 ` [PATCH 3/8] rust: pin-init: internal: add `PhantomInvariant` and `PhantomInvariantLifetime` Gary Guo
2026-05-12 12:09 ` [PATCH 4/8] rust: pin-init: internal: init: handle code blocks early Gary Guo
2026-05-12 12:09 ` [PATCH 5/8] rust: pin-init: internal: use marker on drop guard type for pinned fields Gary Guo
2026-05-12 12:09 ` [PATCH 6/8] rust: pin-init: internal: make `make_closure` inherent methods Gary Guo
2026-05-12 12:09 ` Gary Guo [this message]
2026-05-12 12:09 ` [PATCH 8/8] rust: pin-init: internal: project using full slot Gary Guo
2026-05-14 19:15 ` Gary Guo
2026-05-14 19:31 ` (subset) [PATCH 0/8] rust: pin-init: internal refactors Gary Guo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260512-pin-init-sync-v1-7-81963130dfbd@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.