From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 796EF44EE41; Thu, 8 Jan 2026 13:52:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880353; cv=none; b=eAmbLZGqVC5FeQhLL1s0f872Eur9PnN+sMN5bK8a+sL+rcbqM9qk8mSeoQYvSFzPSBKMeneaesDiMtQ1ae+A1v9fsk34ur+DGgbie2coQjxVD6Y3YqFdPLfcn7QLP5MZh27AsQaWbZjcefz3o8he6mkxsMsUQ9Tv6bMtkaGXFJU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880353; c=relaxed/simple; bh=QwuxWyYwIvM1p8Y2qJaJPDiBKkhZtJoiSkd2kTuj4Lg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=p3dp3D4L/vbnL/0Yz4zgdF5e9ha+3OHawhs8mzz0jvvoTQQgnOERrvLc8fWtaBdZo+nMTJE6iX5jKm58W1xgJwVaXsWFgGmgCr0XBkE5qCmZmAFLTB30yYNAKmwnpy12oRAlI/ycfhguJttNG+lxJWu3HRux4eO0P4TRmraAMes= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=WwywAcwK; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="WwywAcwK" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0A0FAC16AAE; Thu, 8 Jan 2026 13:52:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880352; bh=QwuxWyYwIvM1p8Y2qJaJPDiBKkhZtJoiSkd2kTuj4Lg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WwywAcwKBLLQb/09GG4tF06cwz5OLBWzNFELytRdAuhNTDBBh9NWfWC5lKgGLJXQo A+wCXTqzE06V627v8/ZjxC9I1IrVzUhAEx9HXrEkNcY3d1klZ/5XAhKLTOAD2PA6in yKMCegUXPB2mBhvza+vHDFnyxi6u+zbUvTHNdI5WvR7lmmYyTZWhnDf29ehJw1r0If oq7GvEX3iErXlrlnDdv7KLcqSJypLEA0+kS6vML/Q9I7GgQBJQ/T7U9Yp6AiG2lh99 DM/S+l+UBfY6epakEuhbERu4Nclb3GXuPb6N4O0VV2ptNNqF1YYVgh6RlhoWbJAfo8 hDmjorcTdgaGg== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Christian Schrefl Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 01/12] rust: pin-init: remove `try_` versions of the initializer macros Date: Thu, 8 Jan 2026 14:50:39 +0100 Message-ID: <20260108135127.3153925-2-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The `try_[pin_]init!` versions of the initializer macros are superfluous. Instead of forcing the user to always write an error in `try_[pin_]init!` and not allowing one in `[pin_]init!`, combine them into `[pin_]init!` that defaults the error to `core::convert::Infallible`, but also allows to specify a custom one. Projects using pin-init still can provide their own defaulting initializers using the `try_` prefix by using the `#[default_error]` attribute added in a future patch. [ Adjust the definition of the kernel's version of the `try_` initializer macros - Benno] Signed-off-by: Benno Lossin --- rust/kernel/init.rs | 8 +- rust/pin-init/README.md | 2 +- rust/pin-init/examples/linked_list.rs | 19 ++-- rust/pin-init/examples/pthread_mutex.rs | 10 +- rust/pin-init/src/lib.rs | 118 ++++-------------------- 5 files changed, 35 insertions(+), 122 deletions(-) diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs index 899b9a962762..917f7ef001fd 100644 --- a/rust/kernel/init.rs +++ b/rust/kernel/init.rs @@ -222,14 +222,14 @@ macro_rules! try_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) =3D> { - ::pin_init::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? $crate::error::Error) }; ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) =3D> { - ::pin_init::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? $err) }; @@ -282,14 +282,14 @@ macro_rules! try_pin_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) =3D> { - ::pin_init::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? $crate::error::Error) }; ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) =3D> { - ::pin_init::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? $err) }; diff --git a/rust/pin-init/README.md b/rust/pin-init/README.md index 74bbb4e0a2f7..6cee6ab1eb57 100644 --- a/rust/pin-init/README.md +++ b/rust/pin-init/README.md @@ -135,7 +135,7 @@ struct DriverData { =20 impl DriverData { fn new() -> impl PinInit { - try_pin_init!(Self { + pin_init!(Self { status <- CMutex::new(0), buffer: Box::init(pin_init::init_zeroed())?, }? Error) diff --git a/rust/pin-init/examples/linked_list.rs b/rust/pin-init/examples= /linked_list.rs index f9e117c7dfe0..8445a5890cb7 100644 --- a/rust/pin-init/examples/linked_list.rs +++ b/rust/pin-init/examples/linked_list.rs @@ -6,7 +6,6 @@ =20 use core::{ cell::Cell, - convert::Infallible, marker::PhantomPinned, pin::Pin, ptr::{self, NonNull}, @@ -31,31 +30,31 @@ pub struct ListHead { =20 impl ListHead { #[inline] - pub fn new() -> impl PinInit { - try_pin_init!(&this in Self { + pub fn new() -> impl PinInit { + pin_init!(&this in Self { next: unsafe { Link::new_unchecked(this) }, prev: unsafe { Link::new_unchecked(this) }, pin: PhantomPinned, - }? Infallible) + }) } =20 #[inline] #[allow(dead_code)] - pub fn insert_next(list: &ListHead) -> impl PinInit = + '_ { - try_pin_init!(&this in Self { + pub fn insert_next(list: &ListHead) -> impl PinInit + '_ { + pin_init!(&this in Self { prev: list.next.prev().replace(unsafe { Link::new_unchecked(th= is)}), next: list.next.replace(unsafe { Link::new_unchecked(this)}), pin: PhantomPinned, - }? Infallible) + }) } =20 #[inline] - pub fn insert_prev(list: &ListHead) -> impl PinInit = + '_ { - try_pin_init!(&this in Self { + pub fn insert_prev(list: &ListHead) -> impl PinInit + '_ { + pin_init!(&this in Self { next: list.prev.next().replace(unsafe { Link::new_unchecked(th= is)}), prev: list.prev.replace(unsafe { Link::new_unchecked(this)}), pin: PhantomPinned, - }? Infallible) + }) } =20 #[inline] diff --git a/rust/pin-init/examples/pthread_mutex.rs b/rust/pin-init/exampl= es/pthread_mutex.rs index 49b004c8c137..4e082ec7d5de 100644 --- a/rust/pin-init/examples/pthread_mutex.rs +++ b/rust/pin-init/examples/pthread_mutex.rs @@ -98,11 +98,11 @@ fn init_raw() -> impl PinInit, Error> { // SAFETY: mutex has been initialized unsafe { pin_init_from_closure(init) } } - try_pin_init!(Self { - data: UnsafeCell::new(data), - raw <- init_raw(), - pin: PhantomPinned, - }? Error) + pin_init!(Self { + data: UnsafeCell::new(data), + raw <- init_raw(), + pin: PhantomPinned, + }? Error) } =20 #[allow(dead_code)] diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 8dc9dd5ac6fd..8673008f45d2 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -146,7 +146,7 @@ //! //! impl DriverData { //! fn new() -> impl PinInit { -//! try_pin_init!(Self { +//! pin_init!(Self { //! status <- CMutex::new(0), //! buffer: Box::init(pin_init::init_zeroed())?, //! }? Error) @@ -528,7 +528,7 @@ macro_rules! stack_pin_init { /// x: u32, /// } /// -/// stack_try_pin_init!(let foo: Foo =3D try_pin_init!(Foo { +/// stack_try_pin_init!(let foo: Foo =3D pin_init!(Foo { /// a <- CMutex::new(42), /// b: Box::try_new(Bar { /// x: 64, @@ -555,7 +555,7 @@ macro_rules! stack_pin_init { /// x: u32, /// } /// -/// stack_try_pin_init!(let foo: Foo =3D? try_pin_init!(Foo { +/// stack_try_pin_init!(let foo: Foo =3D? pin_init!(Foo { /// a <- CMutex::new(42), /// b: Box::try_new(Bar { /// x: 64, @@ -584,10 +584,10 @@ macro_rules! stack_try_pin_init { }; } =20 -/// Construct an in-place, pinned initializer for `struct`s. +/// Construct an in-place, fallible pinned initializer for `struct`s. /// -/// This macro defaults the error to [`Infallible`]. If you need a differe= nt error, then use -/// [`try_pin_init!`]. +/// The error type defaults to [`Infallible`]; if you need a different one= , write `? Error` at the +/// end, after the struct initializer. /// /// The syntax is almost identical to that of a normal `struct` initialize= r: /// @@ -783,54 +783,10 @@ macro_rules! pin_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) =3D> { - $crate::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) }; -} - -/// Construct an in-place, fallible pinned initializer for `struct`s. -/// -/// If the initialization can complete without error (or [`Infallible`]), = then use [`pin_init!`]. -/// -/// You can use the `?` operator or use `return Err(err)` inside the initi= alizer to stop -/// initialization and return the error. -/// -/// IMPORTANT: if you have `unsafe` code inside of the initializer you hav= e to ensure that when -/// initialization fails, the memory can be safely deallocated without any= further modifications. -/// -/// The syntax is identical to [`pin_init!`] with the following exception:= you must append `? $type` -/// after the `struct` initializer to specify the error type you want to u= se. -/// -/// # Examples -/// -/// ```rust -/// # #![feature(allocator_api)] -/// # #[path =3D "../examples/error.rs"] mod error; use error::Error; -/// use pin_init::{pin_data, try_pin_init, PinInit, InPlaceInit, init_zero= ed}; -/// -/// #[pin_data] -/// struct BigBuf { -/// big: Box<[u8; 1024 * 1024 * 1024]>, -/// small: [u8; 1024 * 1024], -/// ptr: *mut u8, -/// } -/// -/// impl BigBuf { -/// fn new() -> impl PinInit { -/// try_pin_init!(Self { -/// big: Box::init(init_zeroed())?, -/// small: [0; 1024 * 1024], -/// ptr: core::ptr::null_mut(), -/// }? Error) -/// } -/// } -/// # let _ =3D Box::pin_init(BigBuf::new()); -/// ``` -// For a detailed example of how this macro works, see the module document= ation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! try_pin_init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) =3D> { @@ -847,10 +803,10 @@ macro_rules! try_pin_init { } } =20 -/// Construct an in-place initializer for `struct`s. +/// Construct an in-place, fallible initializer for `struct`s. /// -/// This macro defaults the error to [`Infallible`]. If you need a differe= nt error, then use -/// [`try_init!`]. +/// This macro defaults the error to [`Infallible`]; if you need a differe= nt one, write `? Error` +/// at the end, after the struct initializer. /// /// The syntax is identical to [`pin_init!`] and its safety caveats also a= pply: /// - `unsafe` code must guarantee either full initialization or return an= error and allow @@ -890,52 +846,10 @@ macro_rules! init { ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) =3D> { - $crate::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::init!($(&$this in)? $t $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) - } -} - -/// Construct an in-place fallible initializer for `struct`s. -/// -/// If the initialization can complete without error (or [`Infallible`]), = then use -/// [`init!`]. -/// -/// The syntax is identical to [`try_pin_init!`]. You need to specify a cu= stom error -/// via `? $type` after the `struct` initializer. -/// The safety caveats from [`try_pin_init!`] also apply: -/// - `unsafe` code must guarantee either full initialization or return an= error and allow -/// deallocation of the memory. -/// - the fields are initialized in the order given in the initializer. -/// - no references to fields are allowed to be created inside of the init= ializer. -/// -/// # Examples -/// -/// ```rust -/// # #![feature(allocator_api)] -/// # use core::alloc::AllocError; -/// # use pin_init::InPlaceInit; -/// use pin_init::{try_init, Init, init_zeroed}; -/// -/// struct BigBuf { -/// big: Box<[u8; 1024 * 1024 * 1024]>, -/// small: [u8; 1024 * 1024], -/// } -/// -/// impl BigBuf { -/// fn new() -> impl Init { -/// try_init!(Self { -/// big: Box::init(init_zeroed())?, -/// small: [0; 1024 * 1024], -/// }? AllocError) -/// } -/// } -/// # let _ =3D Box::init(BigBuf::new()); -/// ``` -// For a detailed example of how this macro works, see the module document= ation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! try_init { + }; ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) =3D> { @@ -1410,14 +1324,14 @@ pub fn pin_init_array_from_fn( /// fn init_foo() -> impl PinInit { /// pin_init_scope(|| { /// let bar =3D lookup_bar()?; -/// Ok(try_pin_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// Ok(pin_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) /// }) /// } /// ``` /// /// This initializer will first execute `lookup_bar()`, match on it, if it= returned an error, the /// initializer itself will fail with that error. If it returned `Ok`, the= n it will run the -/// initializer returned by the [`try_pin_init!`] invocation. +/// initializer returned by the [`pin_init!`] invocation. pub fn pin_init_scope(make_init: F) -> impl PinInit where F: FnOnce() -> Result, @@ -1453,14 +1367,14 @@ pub fn pin_init_scope(make_init: F) -> = impl PinInit /// fn init_foo() -> impl Init { /// init_scope(|| { /// let bar =3D lookup_bar()?; -/// Ok(try_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// Ok(init!(Foo { a: bar.a.into(), b: bar.b }? Error)) /// }) /// } /// ``` /// /// This initializer will first execute `lookup_bar()`, match on it, if it= returned an error, the /// initializer itself will fail with that error. If it returned `Ok`, the= n it will run the -/// initializer returned by the [`try_init!`] invocation. +/// initializer returned by the [`init!`] invocation. pub fn init_scope(make_init: F) -> impl Init where F: FnOnce() -> Result, --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 21702461C2A; Thu, 8 Jan 2026 13:52:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880357; cv=none; b=ej/GSTBOM5WCvF+RTTs/ZzruG5l+8ErNkNd9BDjYSl0RwoH/pzWIo17ucjzY3Qyl9ztfmYTxdkq1qSHtNElPHDtR3fCqTSrqzfpzGaWFgTQwBNQPyfHgH9eVO21FI+1B82yQ+i7GQVrbheSaras5g6zblw+E2s+xCWufPPQS6oA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880357; c=relaxed/simple; bh=6glandZGQjyJKagK8Jmi3vQlLhH+2AeXSF2n99rVZCk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AwDhf+lGPVOX6hTms/9HpX3wVjQjat66OjgURtxySCprxpxVYjwLXGcEwuazPgDJzqLovEMVQQ4HWfxLEZ2Sqb+9Xp56oOYea4k050H8H96k3YoW3/nxK5b8SM8BI0wM9s96HsJk2qUu2kOw+ejHJYxP4cPMkzDxkYQRlm+rlaI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=hN8pcJaL; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="hN8pcJaL" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D26CFC116C6; Thu, 8 Jan 2026 13:52:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880356; bh=6glandZGQjyJKagK8Jmi3vQlLhH+2AeXSF2n99rVZCk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hN8pcJaLCQ69EGmBAQWP1KHWc+KGKz/pgIu1juh36XvOdf3imfnlAqtTVtAgU7cCC LNyDm7E7fR1l7aNWsL70wPqsnukZhYT2z+3QHtz1q0rjaADnbc3PhN/8QAa3xZ1aIR LP35+zHJd79pjKODT5nfjqk87Rzk6DXpjfOphnOXihEZoB/0gfbXTdIMkqcHLtH4dc kR82uHfLgynvoAmRiQXnCvReXAb42kQ5Wt0T3SxOxyfjZIDGUHScQisl2JRFKfeCiw QKDqHo3QzskaHWuvYoFKy9cOG7sdCvINNaOXhPuMhqmJhN56Omc20MxZ2KVrGGvtGI leyqcvL3n+LCw== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Christian Schrefl Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 02/12] rust: pin-init: allow the crate to refer to itself as `pin-init` in doc tests Date: Thu, 8 Jan 2026 14:50:40 +0100 Message-ID: <20260108135127.3153925-3-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The `syn` approach requires use of `::pin_init::...` instead of the `$crate::...` construct available to declarative macros. To be able to use the `pin_init` crate from itself (which includes doc tests), we have to declare it as such. Signed-off-by: Benno Lossin --- rust/pin-init/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 8673008f45d2..0e707f00061f 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -290,6 +290,11 @@ ptr::{self, NonNull}, }; =20 +// This is used by doc-tests -- the proc-macros expand to `::pin_init::...= ` and without this the +// doc-tests wouldn't have an extern crate named `pin_init`. +#[allow(unused_extern_crates)] +extern crate self as pin_init; + #[doc(hidden)] pub mod __internal; #[doc(hidden)] --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BAFDF4651E3; Thu, 8 Jan 2026 13:52:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880365; cv=none; b=mR/NBP0vMPLPWxUnUOU84DCZw+4p2K3r2KFDCjyYszNGp+dggwAd1JSRRqx8QjqCxHHC3APQmSa9qjdWZcQAzuIvxcvMg5QcvnVHgR/uUeVtIzpgBi2OlrbUJPFk7uSc0XhfBxZrc5TWmIvAO7WF6r9NMrVwzZVCDJHbB8GDGbs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880365; c=relaxed/simple; bh=H0OGMnbWLBomhQ0XygXSN1BIk0uwjfNpDoj4cYEi8n4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FJR/cjOwXtJocPHgOgGgyBxBdodpe/GGRQWIKhcJkJPTC7Yudofxr6COUOP1hLCvOFB7wCKlimpT1KCmbXP2ZKS7O7iCdnVJ4eY2GT0XtJqA897hzRXC5J8h7vW02CU/ebxZGskt5XIyJqJajs1FD5RpYOBRRzcf6YWwbDqDLLs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=nB5CCkD2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="nB5CCkD2" Received: by smtp.kernel.org (Postfix) with ESMTPSA id ED794C116C6; Thu, 8 Jan 2026 13:52:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880364; bh=H0OGMnbWLBomhQ0XygXSN1BIk0uwjfNpDoj4cYEi8n4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nB5CCkD2qJzh06YYdDN4Va0qnfXaISnsdSbeP7QrNkB5t/ifSu6oaOXpnni+K4n3x jNkSpCOrd7b1YfULdl4eliWK3TV5PiFXDsGrxZQYhKPITXyesbThnzVS5eN9XIITjW hA0bFaynmx68dVTAzRIiJ8n1zCg49j0597H7y9fa2SDWHti13FOdG65Jp0tdSGAjhI AXN2evdA/q8cvLTZlLb780PXeP2MsNyKwJMeVd8unZn0v6sPx4RLL5MVGf1HHPwx1f yLDwoo020G2mooFuMWFX461gpF8euPrZvoYhLI04TKvvFoQC/C1aHz7fUeWuRUVOxk nvjmBiZiMjeQg== From: Benno Lossin To: Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Greg Kroah-Hartman , Christian Schrefl Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 03/12] rust: pin-init: add `syn` dependency and remove `proc-macro[2]` and `quote` workarounds Date: Thu, 8 Jan 2026 14:50:41 +0100 Message-ID: <20260108135127.3153925-4-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" `syn` makes parsing Rust from proc-macros a lot simpler. `pin-init` has not used `syn` up until now, because the we did not support it. That changed in commit 54e3eae85562 ("Merge patch series "`syn` support""), so we can finally utilize the added ergonomics of parsing proc-macro input with `syn`. Previously we only had the `proc-macro` library available, whereas the user-space version also used `proc-macro2` and `quote`. Now both are available, so remove the workarounds. Due to these changes, clippy emits warnings about unnecessary `.to_string()` as `proc-macro2` provides an additional `PartialEq` impl on `Ident`, so the warnings are fixed. [ Adjusted wording from upstream version and added build system changes for the kernel - Benno ] Co-developed-by: Gary Guo Signed-off-by: Gary Guo Signed-off-by: Benno Lossin --- rust/Makefile | 16 ++++++++++------ rust/pin-init/internal/src/helpers.rs | 7 ++----- rust/pin-init/internal/src/lib.rs | 16 ---------------- rust/pin-init/internal/src/pin_data.rs | 18 ++++++------------ rust/pin-init/internal/src/pinned_drop.rs | 10 ++++------ rust/pin-init/internal/src/zeroable.rs | 6 ++---- scripts/generate_rust_analyzer.py | 2 +- 7 files changed, 25 insertions(+), 50 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index 5d357dce1704..96f0ac53ec0e 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -212,9 +212,10 @@ rustdoc-ffi: $(src)/ffi.rs rustdoc-core FORCE =20 rustdoc-pin_init_internal: private rustdoc_host =3D yes rustdoc-pin_init_internal: private rustc_target_flags =3D --cfg kernel \ - --extern proc_macro --crate-type proc-macro + --extern proc_macro --extern proc_macro2 --extern quote --extern syn \ + --crate-type proc-macro rustdoc-pin_init_internal: $(src)/pin-init/internal/src/lib.rs \ - rustdoc-clean FORCE + rustdoc-clean rustdoc-proc_macro2 rustdoc-quote rustdoc-syn FORCE +$(call if_changed,rustdoc) =20 rustdoc-pin_init: private rustdoc_host =3D yes @@ -273,9 +274,10 @@ rusttestlib-macros: $(src)/macros/lib.rs \ +$(call if_changed,rustc_test_library) =20 rusttestlib-pin_init_internal: private rustc_target_flags =3D --cfg kernel= \ - --extern proc_macro + --extern proc_macro --extern proc_macro2 --extern quote --extern syn rusttestlib-pin_init_internal: private rustc_test_library_proc =3D yes -rusttestlib-pin_init_internal: $(src)/pin-init/internal/src/lib.rs FORCE +rusttestlib-pin_init_internal: $(src)/pin-init/internal/src/lib.rs \ + rusttestlib-proc_macro2 rusttestlib-quote rusttestlib-syn FORCE +$(call if_changed,rustc_test_library) =20 rusttestlib-pin_init: private rustc_target_flags =3D --extern pin_init_int= ernal \ @@ -547,8 +549,10 @@ $(obj)/$(libmacros_name): $(src)/macros/lib.rs $(obj)/= libproc_macro2.rlib \ $(obj)/libquote.rlib $(obj)/libsyn.rlib FORCE +$(call if_changed_dep,rustc_procmacro) =20 -$(obj)/$(libpin_init_internal_name): private rustc_target_flags =3D --cfg = kernel -$(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs F= ORCE +$(obj)/$(libpin_init_internal_name): private rustc_target_flags =3D --cfg = kernel \ + --extern proc_macro2 --extern quote --extern syn +$(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs \ + $(obj)/libproc_macro2.rlib $(obj)/libquote.rlib $(obj)/libsyn.rlib FORCE +$(call if_changed_dep,rustc_procmacro) =20 quiet_cmd_rustc_library =3D $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QU= IET)) L $@ diff --git a/rust/pin-init/internal/src/helpers.rs b/rust/pin-init/internal= /src/helpers.rs index 236f989a50f2..90f85eaa4123 100644 --- a/rust/pin-init/internal/src/helpers.rs +++ b/rust/pin-init/internal/src/helpers.rs @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT =20 -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; =20 /// Parsed generics. /// @@ -101,7 +98,7 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Gen= erics, Vec) { 1 =3D> { // Here depending on the token, it might be a gene= ric variable name. match tt.clone() { - TokenTree::Ident(i) if at_start && i.to_string= () =3D=3D "const" =3D> { + TokenTree::Ident(i) if at_start && i =3D=3D "c= onst" =3D> { let Some(name) =3D toks.next() else { // Parsing error. break; diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src= /lib.rs index 297b0129a5bf..4c4dc639ce82 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -7,27 +7,11 @@ //! `pin-init` proc macros. =20 #![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))] -// Allow `.into()` to convert -// - `proc_macro2::TokenStream` into `proc_macro::TokenStream` in the user= -space version. -// - `proc_macro::TokenStream` into `proc_macro::TokenStream` in the kerne= l version. -// Clippy warns on this conversion, but it's required by the user-space = version. -// -// Remove once we have `proc_macro2` in the kernel. -#![allow(clippy::useless_conversion)] // Documentation is done in the pin-init crate instead. #![allow(missing_docs)] =20 use proc_macro::TokenStream; =20 -#[cfg(kernel)] -#[path =3D "../../../macros/quote.rs"] -#[macro_use] -#[cfg_attr(not(kernel), rustfmt::skip)] -mod quote; -#[cfg(not(kernel))] -#[macro_use] -extern crate quote; - mod helpers; mod pin_data; mod pinned_drop; diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/interna= l/src/pin_data.rs index 87d4a7eb1d35..86a53b37cc66 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT =20 -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - use crate::helpers::{parse_generics, Generics}; -use proc_macro::{Group, Punct, Spacing, TokenStream, TokenTree}; +use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree}; +use quote::quote; =20 pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStre= am { // This proc-macro only does some pre-parsing and then delegates the a= ctual parsing to @@ -28,7 +26,7 @@ pub(crate) fn pin_data(args: TokenStream, input: TokenStr= eam) -> TokenStream { // The name of the struct with ty_generics. let struct_name =3D rest .iter() - .skip_while(|tt| !matches!(tt, TokenTree::Ident(i) if i.to_string(= ) =3D=3D "struct")) + .skip_while(|tt| !matches!(tt, TokenTree::Ident(i) if i =3D=3D "st= ruct")) .nth(1) .and_then(|tt| match tt { TokenTree::Ident(_) =3D> { @@ -65,7 +63,7 @@ pub(crate) fn pin_data(args: TokenStream, input: TokenStr= eam) -> TokenStream { .into_iter() .flat_map(|tt| { // We ignore top level `struct` tokens, since they would emit = a compile error. - if matches!(&tt, TokenTree::Ident(i) if i.to_string() =3D=3D "= struct") { + if matches!(&tt, TokenTree::Ident(i) if i =3D=3D "struct") { vec![tt] } else { replace_self_and_deny_type_defs(&struct_name, tt, &mut err= s) @@ -98,11 +96,7 @@ fn replace_self_and_deny_type_defs( ) -> Vec { match tt { TokenTree::Ident(ref i) - if i.to_string() =3D=3D "enum" - || i.to_string() =3D=3D "trait" - || i.to_string() =3D=3D "struct" - || i.to_string() =3D=3D "union" - || i.to_string() =3D=3D "impl" =3D> + if i =3D=3D "enum" || i =3D=3D "trait" || i =3D=3D "struct" ||= i =3D=3D "union" || i =3D=3D "impl" =3D> { errs.extend( format!( @@ -119,7 +113,7 @@ fn replace_self_and_deny_type_defs( ); vec![tt] } - TokenTree::Ident(i) if i.to_string() =3D=3D "Self" =3D> struct_nam= e.clone(), + TokenTree::Ident(i) if i =3D=3D "Self" =3D> struct_name.clone(), TokenTree::Literal(_) | TokenTree::Punct(_) | TokenTree::Ident(_) = =3D> vec![tt], TokenTree::Group(g) =3D> vec![TokenTree::Group(Group::new( g.delimiter(), diff --git a/rust/pin-init/internal/src/pinned_drop.rs b/rust/pin-init/inte= rnal/src/pinned_drop.rs index c4ca7a70b726..cf8cd1c42984 100644 --- a/rust/pin-init/internal/src/pinned_drop.rs +++ b/rust/pin-init/internal/src/pinned_drop.rs @@ -1,15 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT =20 -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; +use quote::quote; =20 pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> Token= Stream { let mut toks =3D input.into_iter().collect::>(); assert!(!toks.is_empty()); // Ensure that we have an `impl` item. - assert!(matches!(&toks[0], TokenTree::Ident(i) if i.to_string() =3D=3D= "impl")); + assert!(matches!(&toks[0], TokenTree::Ident(i) if i =3D=3D "impl")); // Ensure that we are implementing `PinnedDrop`. let mut nesting: usize =3D 0; let mut pinned_drop_idx =3D None; @@ -27,7 +25,7 @@ pub(crate) fn pinned_drop(_args: TokenStream, input: Toke= nStream) -> TokenStream if i >=3D 1 && nesting =3D=3D 0 { // Found the end of the generics, this should be `PinnedDrop`. assert!( - matches!(tt, TokenTree::Ident(i) if i.to_string() =3D=3D "= PinnedDrop"), + matches!(tt, TokenTree::Ident(i) if i =3D=3D "PinnedDrop"), "expected 'PinnedDrop', found: '{tt:?}'" ); pinned_drop_idx =3D Some(i); diff --git a/rust/pin-init/internal/src/zeroable.rs b/rust/pin-init/interna= l/src/zeroable.rs index e0ed3998445c..d8a5ef3883f4 100644 --- a/rust/pin-init/internal/src/zeroable.rs +++ b/rust/pin-init/internal/src/zeroable.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 =20 -#[cfg(not(kernel))] -use proc_macro2 as proc_macro; - use crate::helpers::{parse_generics, Generics}; -use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; +use quote::quote; =20 pub(crate) fn parse_zeroable_derive_input( input: TokenStream, diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_anal= yzer.py index 147d0cc94068..d31d93888658 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -123,7 +123,7 @@ def generate_crates(srctree, objtree, sysroot_src, exte= rnal_src, cfgs, core_edit append_crate( "pin_init_internal", srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs", - [], + ["std", "proc_macro", "proc_macro2", "quote", "syn"], cfg=3D["kernel"], is_proc_macro=3DTrue, ) --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5308A46522C; Thu, 8 Jan 2026 13:52:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880371; cv=none; b=KR+1uXeDUaWkfAcEIK0c0GKyzyKwKUC/mDYBEwJVyLDWrI4wy1N8wVsaTtKH0Hukmuoom+sqe9ngtphGvVBXZV/GjJ2yWX0OWdO7QSChRSOdmYIEIy/DrVUR2lgwekyQhnkT/Y4f7oVyH6dH9qf0F+8lJYSZLYMizkwUdZyaR3E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880371; c=relaxed/simple; bh=PSM2OHqzki9vRcztkStGIeQj+Gz8/CLEbz6NMItYdoI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YSiN5Fq2mxV+0Xsdi9lq2+vgn1bc0hDRGp7neL7kKFzi37W2uqp2MTScxSi1DOf4Nz+XByGM8wGY5Dx1v9KCO1UuYQGaPoT0nvulAowEMZs0qdXn0vzqhTMWNeqQRdvF7XFBWeT1OsvZcHiAEEs43rsHsvjy4rQ5MHBNg2IXlyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=cFYcfVcC; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="cFYcfVcC" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E9FDEC16AAE; Thu, 8 Jan 2026 13:52:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880370; bh=PSM2OHqzki9vRcztkStGIeQj+Gz8/CLEbz6NMItYdoI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cFYcfVcCRbg/p/Vsen7627ouHklDodWQs1GFe7X2UG7iG6mbilkMOweIWaA+eo9cc BZ7ySuQltNZlkVSXeKi+HB7Dsxf1lUD5HJ75IROwmPCyqVe9uN7uvGqYRvs1PehqkE HYlbPhd4d9mlIrXtZtb/eRBgVEQrp0hKBkoO/v+DgULFeMXVVXfp2mPotqSiOn/hMA C8Ux4Af8hV542HTEW0fs0JpvussQRJqWjMoXiJgwzRbzoIinEoa2pmcgds2/PeTPQo 0dpjC2HDWGTVp+kww6R9VJOa5yXU54QfSNWwmtQ04bpqB8nE72zWebTd8amdszJcBZ uFI5wKgR2O5NQ== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Christian Schrefl , Alban Kurti Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 04/12] rust: pin-init: rewrite `derive(Zeroable)` and `derive(MaybeZeroable)` using `syn` Date: Thu, 8 Jan 2026 14:50:42 +0100 Message-ID: <20260108135127.3153925-5-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Rewrite the two derive macros for `Zeroable` using `syn`. One positive side effect of this change is that tuple structs are now supported by them. Additionally, syntax errors and the error emitted when trying to use one of the derive macros on an `enum` are improved. Otherwise no functional changes intended. For example: #[derive(Zeroable)] enum Num { A(u32), B(i32), } Produced this error before this commit: error: no rules expected keyword `enum` --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ no rules expected this token in macro call | note: while trying to match keyword `struct` --> src/macros.rs | | $vis:vis struct $name:ident | ^^^^^^ Now the error is: error: cannot derive `Zeroable` for an enum --> tests/ui/compile-fail/zeroable/enum.rs:5:1 | 5 | enum Num { | ^^^^ error: cannot derive `Zeroable` for an enum Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/lib.rs | 5 +- rust/pin-init/internal/src/zeroable.rs | 149 ++++++++++--------------- rust/pin-init/src/macros.rs | 124 -------------------- 3 files changed, 65 insertions(+), 213 deletions(-) diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src= /lib.rs index 4c4dc639ce82..ec593362c5ac 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -11,6 +11,7 @@ #![allow(missing_docs)] =20 use proc_macro::TokenStream; +use syn::parse_macro_input; =20 mod helpers; mod pin_data; @@ -29,10 +30,10 @@ pub fn pinned_drop(args: TokenStream, input: TokenStrea= m) -> TokenStream { =20 #[proc_macro_derive(Zeroable)] pub fn derive_zeroable(input: TokenStream) -> TokenStream { - zeroable::derive(input.into()).into() + zeroable::derive(parse_macro_input!(input as _)).into() } =20 #[proc_macro_derive(MaybeZeroable)] pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { - zeroable::maybe_derive(input.into()).into() + zeroable::maybe_derive(parse_macro_input!(input as _)).into() } diff --git a/rust/pin-init/internal/src/zeroable.rs b/rust/pin-init/interna= l/src/zeroable.rs index d8a5ef3883f4..0328c3bdfceb 100644 --- a/rust/pin-init/internal/src/zeroable.rs +++ b/rust/pin-init/internal/src/zeroable.rs @@ -1,99 +1,74 @@ // SPDX-License-Identifier: GPL-2.0 =20 -use crate::helpers::{parse_generics, Generics}; -use proc_macro2::{TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse_quote, Data, DeriveInput, Field, Fields}; =20 -pub(crate) fn parse_zeroable_derive_input( - input: TokenStream, -) -> ( - Vec, - Vec, - Vec, - Option, -) { - let ( - Generics { - impl_generics, - decl_generics: _, - ty_generics, - }, - mut rest, - ) =3D parse_generics(input); - // This should be the body of the struct `{...}`. - let last =3D rest.pop(); - // Now we insert `Zeroable` as a bound for every generic parameter in = `impl_generics`. - let mut new_impl_generics =3D Vec::with_capacity(impl_generics.len()); - // Are we inside of a generic where we want to add `Zeroable`? - let mut in_generic =3D !impl_generics.is_empty(); - // Have we already inserted `Zeroable`? - let mut inserted =3D false; - // Level of `<>` nestings. - let mut nested =3D 0; - for tt in impl_generics { - match &tt { - // If we find a `,`, then we have finished a generic/constant/= lifetime parameter. - TokenTree::Punct(p) if nested =3D=3D 0 && p.as_char() =3D=3D '= ,' =3D> { - if in_generic && !inserted { - new_impl_generics.extend(quote! { : ::pin_init::Zeroab= le }); - } - in_generic =3D true; - inserted =3D false; - new_impl_generics.push(tt); - } - // If we find `'`, then we are entering a lifetime. - TokenTree::Punct(p) if nested =3D=3D 0 && p.as_char() =3D=3D '= \'' =3D> { - in_generic =3D false; - new_impl_generics.push(tt); - } - TokenTree::Punct(p) if nested =3D=3D 0 && p.as_char() =3D=3D '= :' =3D> { - new_impl_generics.push(tt); - if in_generic { - new_impl_generics.extend(quote! { ::pin_init::Zeroable= + }); - inserted =3D true; - } - } - TokenTree::Punct(p) if p.as_char() =3D=3D '<' =3D> { - nested +=3D 1; - new_impl_generics.push(tt); - } - TokenTree::Punct(p) if p.as_char() =3D=3D '>' =3D> { - assert!(nested > 0); - nested -=3D 1; - new_impl_generics.push(tt); - } - _ =3D> new_impl_generics.push(tt), +pub(crate) fn derive(input: DeriveInput) -> TokenStream { + let fields =3D match input.data { + Data::Struct(data_struct) =3D> data_struct.fields, + Data::Union(data_union) =3D> Fields::Named(data_union.fields), + Data::Enum(data_enum) =3D> { + return quote_spanned! {data_enum.enum_token.span=3D> + ::core::compile_error!("cannot derive `Zeroable` for an en= um"); + }; } + }; + let name =3D input.ident; + let mut generics =3D input.generics; + for param in generics.type_params_mut() { + param.bounds.insert(0, parse_quote!(::pin_init::Zeroable)); } - assert_eq!(nested, 0); - if in_generic && !inserted { - new_impl_generics.extend(quote! { : ::pin_init::Zeroable }); - } - (rest, new_impl_generics, ty_generics, last) -} - -pub(crate) fn derive(input: TokenStream) -> TokenStream { - let (rest, new_impl_generics, ty_generics, last) =3D parse_zeroable_de= rive_input(input); + let (impl_gen, ty_gen, whr) =3D generics.split_for_impl(); + let field_type =3D fields.iter().map(|field| &field.ty); quote! { - ::pin_init::__derive_zeroable!( - parse_input: - @sig(#(#rest)*), - @impl_generics(#(#new_impl_generics)*), - @ty_generics(#(#ty_generics)*), - @body(#last), - ); + // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. + #[automatically_derived] + unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen + #whr + {} + const _: () =3D { + fn assert_zeroable() {} + fn ensure_zeroable #impl_gen () + #whr + { + #( + assert_zeroable::<#field_type>(); + )* + } + }; } } =20 -pub(crate) fn maybe_derive(input: TokenStream) -> TokenStream { - let (rest, new_impl_generics, ty_generics, last) =3D parse_zeroable_de= rive_input(input); +pub(crate) fn maybe_derive(input: DeriveInput) -> TokenStream { + let fields =3D match input.data { + Data::Struct(data_struct) =3D> data_struct.fields, + Data::Union(data_union) =3D> Fields::Named(data_union.fields), + Data::Enum(data_enum) =3D> { + return quote_spanned! {data_enum.enum_token.span=3D> + compile_error!("cannot derive `Zeroable` for an enum"); + }; + } + }; + let name =3D input.ident; + let mut generics =3D input.generics; + for param in generics.type_params_mut() { + param.bounds.insert(0, parse_quote!(::pin_init::Zeroable)); + } + for Field { ty, .. } in fields { + generics + .make_where_clause() + .predicates + // the `for<'__dummy>` HRTB makes this not error without the `= trivial_bounds` + // feature . + .push(parse_quote!(#ty: for<'__dummy> ::pin_init::Zeroable)); + } + let (impl_gen, ty_gen, whr) =3D generics.split_for_impl(); quote! { - ::pin_init::__maybe_derive_zeroable!( - parse_input: - @sig(#(#rest)*), - @impl_generics(#(#new_impl_generics)*), - @ty_generics(#(#ty_generics)*), - @body(#last), - ); + // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. + #[automatically_derived] + unsafe impl #impl_gen ::pin_init::Zeroable for #name #ty_gen + #whr + {} } } diff --git a/rust/pin-init/src/macros.rs b/rust/pin-init/src/macros.rs index 682c61a587a0..53ed5ce860fc 100644 --- a/rust/pin-init/src/macros.rs +++ b/rust/pin-init/src/macros.rs @@ -1551,127 +1551,3 @@ fn assert_zeroable(_: *mut T) = {} ); }; } - -#[doc(hidden)] -#[macro_export] -macro_rules! __derive_zeroable { - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) =3D> { - // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_g= enerics)*> - where - $($($whr)*)? - {} - const _: () =3D { - fn assert_zeroable() {} - fn ensure_zeroable<$($impl_generics)*>() - where $($($whr)*)? - { - $(assert_zeroable::<$field_ty>();)* - } - }; - }; - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis union $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) =3D> { - // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_g= enerics)*> - where - $($($whr)*)? - {} - const _: () =3D { - fn assert_zeroable() {} - fn ensure_zeroable<$($impl_generics)*>() - where $($($whr)*)? - { - $(assert_zeroable::<$field_ty>();)* - } - }; - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __maybe_derive_zeroable { - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) =3D> { - // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_g= enerics)*> - where - $( - // the `for<'__dummy>` HRTB makes this not error without t= he `trivial_bounds` - // feature . - $field_ty: for<'__dummy> $crate::Zeroable, - )* - $($($whr)*)? - {} - }; - (parse_input: - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis union $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @body({ - $( - $(#[$($field_attr:tt)*])* - $field_vis:vis $field:ident : $field_ty:ty - ),* $(,)? - }), - ) =3D> { - // SAFETY: Every field type implements `Zeroable` and padding byte= s may be zero. - #[automatically_derived] - unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_g= enerics)*> - where - $( - // the `for<'__dummy>` HRTB makes this not error without t= he `trivial_bounds` - // feature . - $field_ty: for<'__dummy> $crate::Zeroable, - )* - $($($whr)*)? - {} - }; -} --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4F99947798E; Thu, 8 Jan 2026 13:52:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880377; cv=none; b=NPejzwd1WQ9yuwvgTBc6GidyxHUbKhgjcp0M6zc7+azSYohK+GL4WhoV9Ur+TYqoTkJyeEoWcXAZgpS2/UytcHC/xiX2WCDtdwxBRc00Zkx8Y0HXgzqcvnD9bBMX6B28uuqjtbK+29ww9FZ71e5AVzyPUDmdcj7WLC/mLNGrKA8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880377; c=relaxed/simple; bh=C4wmA+o78ngIWU6MAnEP1QTsUr781e8j9Hqr/t5dETU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NuK3ngyPrv8NBWGBJIxCfFaztQqnpaBV3v8skAnuUSkX6Yv1zk+R2cIlHPhlniPCqXxviWF+hB11elXa2253og1Hp4lx3dmbhX9/mumiB46/xz6ojDsNxsgG7Mbq2wMW4F/Vzl9nmBMcAiweyhDM5nCebWZCALv85OhSnOLkJM4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=oUdBcHWi; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="oUdBcHWi" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D6B54C116C6; Thu, 8 Jan 2026 13:52:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880376; bh=C4wmA+o78ngIWU6MAnEP1QTsUr781e8j9Hqr/t5dETU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oUdBcHWiBsI2bIwF7kAsnpiIbdC9XJSLZW4F7nqVdGTJ9RUzHHnGY6J7So6HfGkcc XEyw2mh8woAea2eiIRh5BcJaHL9FjeC6vu9JseY0KXtTYmVEx6ebPNdfSJn9p7IxKb q9BZgesaUIoJobjBCE7H7lrd5NoQXKwtAFevh90xNmilQIKAB5KVecNQuQhy9OhRug sNfDT6N+aPJR0Lob0Kciyr8nXA4hyf9JE3KEfSXaa8qQh1KkStrXkWZiECsxcU3/lH GSBRT/tmRx5fpwyWnqyxCpQPu7Gmbvx/eTX5RUppgJjSC4vdx0V9T+q42i1rPXFDCZ Rnkrxmr/6Rkkw== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Tamir Duberstein , Alban Kurti Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 05/12] rust: pin-init: rewrite the `#[pinned_drop]` attribute macro using `syn` Date: Thu, 8 Jan 2026 14:50:43 +0100 Message-ID: <20260108135127.3153925-6-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Rewrite the attribute macro for implementing `PinnedDrop` using `syn`. Otherwise no functional changes intended aside from improved error messages on syntactic and semantical errors. For example: When missing the `drop` function in the implementation, the old error was: error: no rules expected `)` --> tests/ui/compile-fail/pinned_drop/no_fn.rs:6:1 | 6 | #[pinned_drop] | ^^^^^^^^^^^^^^ no rules expected this token in macro call | note: while trying to match keyword `fn` --> src/macros.rs | | fn drop($($sig:tt)*) { | ^^ =3D note: this error originates in the attribute macro `pinned_drop` = (in Nightly builds, run with -Z macro-backtrace for more info) And the new one is: error[E0046]: not all trait items implemented, missing: `drop` --> tests/ui/compile-fail/pinned_drop/no_fn.rs:7:1 | 7 | impl PinnedDrop for Foo {} | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation | =3D help: implement the missing item: `fn drop(self: Pin<&mut Self>, = _: OnlyCallFromDrop) { todo!() }` Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/lib.rs | 6 +- rust/pin-init/internal/src/pinned_drop.rs | 87 ++++++++++++----------- rust/pin-init/src/macros.rs | 28 -------- 3 files changed, 52 insertions(+), 69 deletions(-) diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src= /lib.rs index ec593362c5ac..d0156b82b5d3 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -25,7 +25,11 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -= > TokenStream { =20 #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { - pinned_drop::pinned_drop(args.into(), input.into()).into() + pinned_drop::pinned_drop( + parse_macro_input!(args as _), + parse_macro_input!(input as _), + ) + .into() } =20 #[proc_macro_derive(Zeroable)] diff --git a/rust/pin-init/internal/src/pinned_drop.rs b/rust/pin-init/inte= rnal/src/pinned_drop.rs index cf8cd1c42984..4df2cb9959fb 100644 --- a/rust/pin-init/internal/src/pinned_drop.rs +++ b/rust/pin-init/internal/src/pinned_drop.rs @@ -1,49 +1,56 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT =20 -use proc_macro2::{TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse::Nothing, parse_quote, spanned::Spanned, ImplItem, ItemImp= l, Token}; =20 -pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> Token= Stream { - let mut toks =3D input.into_iter().collect::>(); - assert!(!toks.is_empty()); - // Ensure that we have an `impl` item. - assert!(matches!(&toks[0], TokenTree::Ident(i) if i =3D=3D "impl")); - // Ensure that we are implementing `PinnedDrop`. - let mut nesting: usize =3D 0; - let mut pinned_drop_idx =3D None; - for (i, tt) in toks.iter().enumerate() { - match tt { - TokenTree::Punct(p) if p.as_char() =3D=3D '<' =3D> { - nesting +=3D 1; +pub(crate) fn pinned_drop(_args: Nothing, mut input: ItemImpl) -> TokenStr= eam { + let mut errors =3D vec![]; + if let Some(unsafety) =3D input.unsafety { + errors.push(quote_spanned! {unsafety.span=3D> + ::core::compile_error!("implementing `PinnedDrop` is safe"); + }); + } + input.unsafety =3D Some(Token![unsafe](input.impl_token.span)); + match &mut input.trait_ { + Some((not, path, _for)) =3D> { + if let Some(not) =3D not { + errors.push(quote_spanned! {not.span=3D> + ::core::compile_error!("cannot implement `!PinnedDrop`= "); + }); } - TokenTree::Punct(p) if p.as_char() =3D=3D '>' =3D> { - nesting =3D nesting.checked_sub(1).unwrap(); - continue; + for (seg, expected) in path + .segments + .iter() + .rev() + .zip(["PinnedDrop", "pin_init", ""]) + { + if expected.is_empty() || seg.ident !=3D expected { + errors.push(quote_spanned! {seg.span()=3D> + ::core::compile_error!("bad import path for `Pinne= dDrop`"); + }); + } + if !seg.arguments.is_none() { + errors.push(quote_spanned! {seg.arguments.span()=3D> + ::core::compile_error!("unexpected arguments for `= PinnedDrop` path"); + }); + } } - _ =3D> {} - } - if i >=3D 1 && nesting =3D=3D 0 { - // Found the end of the generics, this should be `PinnedDrop`. - assert!( - matches!(tt, TokenTree::Ident(i) if i =3D=3D "PinnedDrop"), - "expected 'PinnedDrop', found: '{tt:?}'" - ); - pinned_drop_idx =3D Some(i); - break; + *path =3D parse_quote!(::pin_init::PinnedDrop); } + None =3D> errors.push(quote_spanned! {input.impl_token.span=3D> + ::core::compile_error!("expected `impl ... PinnedDrop for ...`= , got inherent impl"); + }), } - let idx =3D pinned_drop_idx - .unwrap_or_else(|| panic!("Expected an `impl` block implementing `= PinnedDrop`.")); - // Fully qualify the `PinnedDrop`, as to avoid any tampering. - toks.splice(idx..idx, quote!(::pin_init::)); - // Take the `{}` body and call the declarative macro. - if let Some(TokenTree::Group(last)) =3D toks.pop() { - let last =3D last.stream(); - quote!(::pin_init::__pinned_drop! { - @impl_sig(#(#toks)*), - @impl_body(#last), - }) - } else { - TokenStream::from_iter(toks) + for item in &mut input.items { + if let ImplItem::Fn(fn_item) =3D item { + if fn_item.sig.ident =3D=3D "drop" { + fn_item + .sig + .inputs + .push(parse_quote!(_: ::pin_init::__internal::OnlyCall= FromDrop)); + } + } } + quote!(#(#errors)* #input) } diff --git a/rust/pin-init/src/macros.rs b/rust/pin-init/src/macros.rs index 53ed5ce860fc..b80c95612fd6 100644 --- a/rust/pin-init/src/macros.rs +++ b/rust/pin-init/src/macros.rs @@ -503,34 +503,6 @@ #[cfg(not(kernel))] pub use ::paste::paste; =20 -/// Creates a `unsafe impl<...> PinnedDrop for $type` block. -/// -/// See [`PinnedDrop`] for more information. -/// -/// [`PinnedDrop`]: crate::PinnedDrop -#[doc(hidden)] -#[macro_export] -macro_rules! __pinned_drop { - ( - @impl_sig($($impl_sig:tt)*), - @impl_body( - $(#[$($attr:tt)*])* - fn drop($($sig:tt)*) { - $($inner:tt)* - } - ), - ) =3D> { - // SAFETY: TODO. - unsafe $($impl_sig)* { - // Inherit all attributes and the type/ident tokens for the si= gnature. - $(#[$($attr)*])* - fn drop($($sig)*, _: $crate::__internal::OnlyCallFromDrop) { - $($inner)* - } - } - } -} - /// This macro first parses the struct definition such that it separates p= inned and not pinned /// fields. Afterwards it declares the struct and implement the `PinData` = trait safely. #[doc(hidden)] --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ABCF9466B6B; Thu, 8 Jan 2026 13:53:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880385; cv=none; b=Ls7lN2nhzXnrRxqBgq2QhY6ocjq2U7G0+tU1rgp8RvMsUiBh6m8RegBgjDj+wzbtM50suZCgLD8TgxGEufE7nNfSvrqKgoU1Y6fiVf79JrmtT5z/gcXtEM9HR+aTpT2USqe2wWucStRiXnMmrrjOJFRBpFC5l2MRSVQDPNRrNlY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880385; c=relaxed/simple; bh=IR8dDkfMwr4GGRSsKFX342L4Wo4nqFSKNJoFEfLz4xw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OH7K8cHYhBRjFxMsbGlXPfibUk0Y7Rr2hqI8JSbTG3Z+ez6HZcjN5814GNIXOtVEO9ZrmpcsPtjfj4mAEs4LsfzJkzysIWy6L0IvSp2yBAVYiC/dyQ6r2YK7ZrR/OcnDDacutkatJhg2wjWKGPRJTxnTyAc06ZuCP2GyrEhNbzw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mZR01hG8; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="mZR01hG8" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4DA55C16AAE; Thu, 8 Jan 2026 13:53:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880385; bh=IR8dDkfMwr4GGRSsKFX342L4Wo4nqFSKNJoFEfLz4xw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mZR01hG8tpUH6Hauk6ILTLNe8aF3YqNL1ePVFKyo1FPd/Tw0rslyyWp7Zqr6p5W0V qIZ56phCGuYODEiUKGZ69pLLWrZKfkuJkGG7WgWY+QAX54u2/gdviyRl8vdNy2aAlB 39jeCZ8fCfbw7EEwup24xVNTEXxlrF9iVPqaCod25FAORBdtj/4WpFOAbaktBnrxft M98V74YZlZ6FSDl66JIVV9j8lC+P1rQmKP/GQvTt9UWQc3FOJoHFnaL3rkvf4Q9O9t vBwZIZTyBrQ0SN/FNEUsE1wDSaJf2gH5G86q6Oo1vd6RIZ/3OAUpNocEhdUPXIntTT lPokeRdxsS7ww== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Greg Kroah-Hartman , Alban Kurti Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org Subject: [PATCH 06/12] rust: pin-init: rewrite `#[pin_data]` using `syn` Date: Thu, 8 Jan 2026 14:50:44 +0100 Message-ID: <20260108135127.3153925-7-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Rewrite the attribute macro `#[pin_data]` using `syn`. No functional changes intended aside from improved error messages on syntactic and semantical errors. For example if one forgets a comma at the end of a field: #[pin_data] struct Foo { a: Box b: Box } The declarative macro reports the following errors: error: expected `,`, or `}`, found `b` --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16 | 5 | a: Box | ^ help: try adding a comma: `,` error: recursion limit reached while expanding `$crate::__pin_data!` --> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1 | 3 | #[pin_data] | ^^^^^^^^^^^ | =3D help: consider increasing the recursion limit by adding a `#![rec= ursion_limit =3D "256"]` attribute to your crate (`$CRATE`) =3D note: this error originates in the macro `$crate::__pin_data` whi= ch comes from the expansion of the attribute macro `pin_data` (in Nightly b= uilds, run with -Z macro-backtrace for more info) The new `syn` version reports: error: expected `,`, or `}`, found `b` --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16 | 5 | a: Box | ^ help: try adding a comma: `,` error: expected `,` --> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5 | 6 | b: Box | ^ Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/helpers.rs | 149 ------ rust/pin-init/internal/src/lib.rs | 12 +- rust/pin-init/internal/src/pin_data.rs | 645 ++++++++++++++++++++----- rust/pin-init/src/macros.rs | 574 ---------------------- 4 files changed, 543 insertions(+), 837 deletions(-) delete mode 100644 rust/pin-init/internal/src/helpers.rs diff --git a/rust/pin-init/internal/src/helpers.rs b/rust/pin-init/internal= /src/helpers.rs deleted file mode 100644 index 90f85eaa4123..000000000000 --- a/rust/pin-init/internal/src/helpers.rs +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use proc_macro2::{TokenStream, TokenTree}; - -/// Parsed generics. -/// -/// See the field documentation for an explanation what each of the fields= represents. -/// -/// # Examples -/// -/// ```rust,ignore -/// # let input =3D todo!(); -/// let (Generics { decl_generics, impl_generics, ty_generics }, rest) =3D= parse_generics(input); -/// quote! { -/// struct Foo<$($decl_generics)*> { -/// // ... -/// } -/// -/// impl<$impl_generics> Foo<$ty_generics> { -/// fn foo() { -/// // ... -/// } -/// } -/// } -/// ``` -pub(crate) struct Generics { - /// The generics with bounds and default values (e.g. `T: Clone, const= N: usize =3D 0`). - /// - /// Use this on type definitions e.g. `struct Foo<$decl_generics> ...`= (or `union`/`enum`). - pub(crate) decl_generics: Vec, - /// The generics with bounds (e.g. `T: Clone, const N: usize`). - /// - /// Use this on `impl` blocks e.g. `impl<$impl_generics> Trait for ...= `. - pub(crate) impl_generics: Vec, - /// The generics without bounds and without default values (e.g. `T, N= `). - /// - /// Use this when you use the type that is declared with these generic= s e.g. - /// `Foo<$ty_generics>`. - pub(crate) ty_generics: Vec, -} - -/// Parses the given `TokenStream` into `Generics` and the rest. -/// -/// The generics are not present in the rest, but a where clause might rem= ain. -pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec) { - // The generics with bounds and default values. - let mut decl_generics =3D vec![]; - // `impl_generics`, the declared generics with their bounds. - let mut impl_generics =3D vec![]; - // Only the names of the generics, without any bounds. - let mut ty_generics =3D vec![]; - // Tokens not related to the generics e.g. the `where` token and defin= ition. - let mut rest =3D vec![]; - // The current level of `<`. - let mut nesting =3D 0; - let mut toks =3D input.into_iter(); - // If we are at the beginning of a generic parameter. - let mut at_start =3D true; - let mut skip_until_comma =3D false; - while let Some(tt) =3D toks.next() { - if nesting =3D=3D 1 && matches!(&tt, TokenTree::Punct(p) if p.as_c= har() =3D=3D '>') { - // Found the end of the generics. - break; - } else if nesting >=3D 1 { - decl_generics.push(tt.clone()); - } - match tt.clone() { - TokenTree::Punct(p) if p.as_char() =3D=3D '<' =3D> { - if nesting >=3D 1 && !skip_until_comma { - // This is inside of the generics and part of some bou= nd. - impl_generics.push(tt); - } - nesting +=3D 1; - } - TokenTree::Punct(p) if p.as_char() =3D=3D '>' =3D> { - // This is a parsing error, so we just end it here. - if nesting =3D=3D 0 { - break; - } else { - nesting -=3D 1; - if nesting >=3D 1 && !skip_until_comma { - // We are still inside of the generics and part of= some bound. - impl_generics.push(tt); - } - } - } - TokenTree::Punct(p) if skip_until_comma && p.as_char() =3D=3D = ',' =3D> { - if nesting =3D=3D 1 { - impl_generics.push(tt.clone()); - impl_generics.push(tt); - skip_until_comma =3D false; - } - } - _ if !skip_until_comma =3D> { - match nesting { - // If we haven't entered the generics yet, we still wa= nt to keep these tokens. - 0 =3D> rest.push(tt), - 1 =3D> { - // Here depending on the token, it might be a gene= ric variable name. - match tt.clone() { - TokenTree::Ident(i) if at_start && i =3D=3D "c= onst" =3D> { - let Some(name) =3D toks.next() else { - // Parsing error. - break; - }; - impl_generics.push(tt); - impl_generics.push(name.clone()); - ty_generics.push(name.clone()); - decl_generics.push(name); - at_start =3D false; - } - TokenTree::Ident(_) if at_start =3D> { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - at_start =3D false; - } - TokenTree::Punct(p) if p.as_char() =3D=3D ',' = =3D> { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - at_start =3D true; - } - // Lifetimes begin with `'`. - TokenTree::Punct(p) if p.as_char() =3D=3D '\''= && at_start =3D> { - impl_generics.push(tt.clone()); - ty_generics.push(tt); - } - // Generics can have default values, we skip t= hese. - TokenTree::Punct(p) if p.as_char() =3D=3D '=3D= ' =3D> { - skip_until_comma =3D true; - } - _ =3D> impl_generics.push(tt), - } - } - _ =3D> impl_generics.push(tt), - } - } - _ =3D> {} - } - } - rest.extend(toks); - ( - Generics { - impl_generics, - decl_generics, - ty_generics, - }, - rest, - ) -} diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src= /lib.rs index d0156b82b5d3..243684a1eedc 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -13,14 +13,20 @@ use proc_macro::TokenStream; use syn::parse_macro_input; =20 -mod helpers; mod pin_data; mod pinned_drop; mod zeroable; =20 #[proc_macro_attribute] -pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream { - pin_data::pin_data(inner.into(), item.into()).into() +pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { + match pin_data::pin_data( + parse_macro_input!(args as _), + parse_macro_input!(input as _), + ) { + Ok(stream) =3D> stream, + Err(err) =3D> err.into_compile_error(), + } + .into() } =20 #[proc_macro_attribute] diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/interna= l/src/pin_data.rs index 86a53b37cc66..d1e7ed121860 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1,126 +1,549 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT =20 -use crate::helpers::{parse_generics, Generics}; -use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree}; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + parse::{End, Nothing, Parse}, + parse_quote, parse_quote_spanned, + spanned::Spanned, + visit_mut::VisitMut, + Error, Field, Ident, Item, ItemStruct, PathSegment, Result, Type, Type= Path, WhereClause, +}; =20 -pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStre= am { - // This proc-macro only does some pre-parsing and then delegates the a= ctual parsing to - // `pin_init::__pin_data!`. +pub(crate) mod kw { + syn::custom_keyword!(PinnedDrop); +} + +pub(crate) enum Args { + Nothing(Nothing), + #[allow(dead_code)] + PinnedDrop(kw::PinnedDrop), +} + +impl Parse for Args { + fn parse(input: syn::parse::ParseStream) -> Result { + let lh =3D input.lookahead1(); + if lh.peek(End) { + input.parse().map(Self::Nothing) + } else if lh.peek(kw::PinnedDrop) { + let res =3D input.parse().map(Self::PinnedDrop)?; + let lh =3D input.lookahead1(); + if lh.peek(End) { + Ok(res) + } else { + Err(lh.error()) + } + } else { + Err(lh.error()) + } + } +} + +pub(crate) fn pin_data(args: Args, input: Item) -> Result { + let mut struct_ =3D match input { + Item::Struct(struct_) =3D> struct_, + Item::Enum(enum_) =3D> { + return Err(Error::new_spanned( + enum_.enum_token, + "`#[pin_data]` only supports structs for now", + )); + } + Item::Union(union) =3D> { + return Err(Error::new_spanned( + union.union_token, + "`#[pin_data]` only supports structs for now", + )); + } + rest =3D> { + return Err(Error::new_spanned( + rest, + "`#[pin_data]` can only be applied to struct, enum and uni= on defintions", + )); + } + }; + + // The generics might contain the `Self` type. Since this macro will d= efine a new type with the + // same generics and bounds, this poses a problem: `Self` will refer t= o the new type as opposed + // to this struct definition. Therefore we have to replace `Self` with= the concrete name. + let mut replacer =3D { + let name =3D &struct_.ident; + let (_, ty_generics, _) =3D struct_.generics.split_for_impl(); + SelfReplacer(parse_quote!(#name #ty_generics)) + }; + replacer.visit_generics_mut(&mut struct_.generics); + replacer.visit_fields_mut(&mut struct_.fields); + + let mut error: Option =3D None; + for field in &struct_.fields { + if !is_field_structurally_pinned(field) && is_phantom_pinned(&fiel= d.ty) { + let mut err =3D Error::new_spanned( + field, + format!( + "The field `{}` of type `PhantomData` only has an effe= ct \ + if it has the `#[pin]` attribute", + field.ident.as_ref().expect(""), + ), + ); + if let Some(mut error) =3D error.take() { + error.combine(err); + err =3D error; + } + error =3D Some(err); + } + } + + let unpin_impl =3D generate_unpin_impl(&struct_); + let drop_impl =3D generate_drop_impl(&struct_, args); + let projections =3D generate_projections(&struct_); + let the_pin_data =3D generate_the_pin_data(&struct_); + + strip_pin_annotations(&mut struct_); + + let error =3D error.map(|e| e.into_compile_error()); + + Ok(quote! { + #struct_ + #projections + // We put the rest into this const item, because it then will not = be accessible to anything + // outside. + const _: () =3D { + #the_pin_data + #unpin_impl + #drop_impl + }; + #error + }) +} + +fn is_phantom_pinned(ty: &Type) -> bool { + match ty { + Type::Path(TypePath { qself: None, path }) =3D> { + // Cannot possibly refer to `PhantomPinned` (except alias, but= that's on the user). + if path.segments.len() > 3 { + return false; + } + // If there is a `::`, then the path needs to be `::core::mark= er::PhantomPinned` or + // `::std::marker::PhantomPinned`. + if path.leading_colon.is_some() && path.segments.len() !=3D 3 { + return false; + } + let expected: Vec<&[&str]> =3D vec![&["PhantomPinned"], &["mar= ker"], &["core", "std"]]; + for (actual, expected) in path.segments.iter().rev().zip(expec= ted) { + if !actual.arguments.is_empty() || expected.iter().all(|e|= actual.ident !=3D e) { + return false; + } + } + true + } + _ =3D> false, + } +} + +fn is_field_structurally_pinned(field: &Field) -> bool { + field.attrs.iter().any(|a| a.path().is_ident("pin")) +} =20 +fn generate_unpin_impl( + ItemStruct { + ident, + generics, + fields, + .. + }: &ItemStruct, +) -> TokenStream { + let generics_with_pinlt =3D { + let mut g =3D generics.clone(); + g.params.insert(0, parse_quote!('__pin)); + let _ =3D g.make_where_clause(); + g + }; let ( - Generics { - impl_generics, - decl_generics, - ty_generics, - }, - rest, - ) =3D parse_generics(input); - // The struct definition might contain the `Self` type. Since `__pin_d= ata!` will define a new - // type with the same generics and bounds, this poses a problem, since= `Self` will refer to the - // new type as opposed to this struct definition. Therefore we have to= replace `Self` with the - // concrete name. - - // Errors that occur when replacing `Self` with `struct_name`. - let mut errs =3D TokenStream::new(); - // The name of the struct with ty_generics. - let struct_name =3D rest + impl_generics_with_pinlt, + ty_generics_with_pinlt, + Some(WhereClause { + where_token, + predicates, + }), + ) =3D generics_with_pinlt.split_for_impl() + else { + unreachable!() + }; + let (_, ty_generics, _) =3D generics.split_for_impl(); + let mut pinned_fields =3D fields .iter() - .skip_while(|tt| !matches!(tt, TokenTree::Ident(i) if i =3D=3D "st= ruct")) - .nth(1) - .and_then(|tt| match tt { - TokenTree::Ident(_) =3D> { - let tt =3D tt.clone(); - let mut res =3D vec![tt]; - if !ty_generics.is_empty() { - // We add this, so it is maximally compatible with e.g= . `Self::CONST` which - // will be replaced by `StructName::<$generics>::CONST= `. - res.push(TokenTree::Punct(Punct::new(':', Spacing::Joi= nt))); - res.push(TokenTree::Punct(Punct::new(':', Spacing::Alo= ne))); - res.push(TokenTree::Punct(Punct::new('<', Spacing::Alo= ne))); - res.extend(ty_generics.iter().cloned()); - res.push(TokenTree::Punct(Punct::new('>', Spacing::Alo= ne))); + .filter(|f| is_field_structurally_pinned(f)) + .cloned() + .collect::>(); + for field in &mut pinned_fields { + field.attrs.retain(|a| !a.path().is_ident("pin")); + } + 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`. + #[allow(dead_code)] // The fields below are never used. + struct __Unpin #generics_with_pinlt + #where_token + #predicates + { + __phantom_pin: ::core::marker::PhantomData &= '__pin ()>, + __phantom: ::core::marker::PhantomData< + fn(#ident #ty_generics) -> #ident #ty_generics + >, + #(#pinned_fields),* + } + + #[doc(hidden)] + impl #impl_generics_with_pinlt ::core::marker::Unpin for #ident #t= y_generics + #where_token + __Unpin #ty_generics_with_pinlt: ::core::marker::Unpin, + #predicates + {} + } +} + +fn generate_drop_impl( + ItemStruct { + ident, generics, .. + }: &syn::ItemStruct, + args: Args, +) -> TokenStream { + let (impl_generics, ty_generics, whr) =3D generics.split_for_impl(); + let has_pinned_drop =3D matches!(args, Args::PinnedDrop(_)); + // We need to disallow normal `Drop` implementation, the exact behavio= r depends on whether + // `PinnedDrop` was specified in `args`. + if has_pinned_drop { + // When `PinnedDrop` was specified we just implement `Drop` and de= legate. + quote! { + impl #impl_generics ::core::ops::Drop for #ident #ty_generics + #whr + { + fn drop(&mut self) { + // SAFETY: Since this is a destructor, `self` will not= move after this function + // terminates, since it is inaccessible. + let pinned =3D unsafe { ::core::pin::Pin::new_unchecke= d(self) }; + // SAFETY: Since this is a drop function, we can creat= e this token to call the + // pinned destructor of this type. + let token =3D unsafe { ::pin_init::__internal::OnlyCal= lFromDrop::new() }; + ::pin_init::PinnedDrop::drop(pinned, token); } - Some(res) } - _ =3D> None, - }) - .unwrap_or_else(|| { - // If we did not find the name of the struct then we will use = `Self` as the replacement - // and add a compile error to ensure it does not compile. - errs.extend( - "::core::compile_error!(\"Could not locate type name.\");" - .parse::() - .unwrap(), - ); - "Self".parse::().unwrap().into_iter().collect() - }); - let impl_generics =3D impl_generics - .into_iter() - .flat_map(|tt| replace_self_and_deny_type_defs(&struct_name, tt, &= mut errs)) - .collect::>(); - let mut rest =3D rest - .into_iter() - .flat_map(|tt| { - // We ignore top level `struct` tokens, since they would emit = a compile error. - if matches!(&tt, TokenTree::Ident(i) if i =3D=3D "struct") { - vec![tt] + } + } else { + // When no `PinnedDrop` was specified, then we have to prevent imp= lementing drop. + quote! { + // We prevent this by creating a trait that will be implemente= d for all types implementing + // `Drop`. Additionally we will implement this trait for the s= truct leading to a conflict, + // if it also implements `Drop` + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl #impl_generics MustNotImplDrop for #ident #ty_generics + #whr + {} + // We also take care to prevent users from writing a useless `= PinnedDrop` implementation. + // They might implement `PinnedDrop` correctly for the struct,= but forget to give + // `PinnedDrop` as the parameter to `#[pin_data]`. + #[expect(non_camel_case_types)] + trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} + impl + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T= {} + impl #impl_generics + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #= ident #ty_generics + #whr + {} + } + } +} + +fn generate_projections( + ItemStruct { + vis, + ident, + generics, + fields, + .. + }: &ItemStruct, +) -> TokenStream { + let (og_impl_gen, og_ty_gen, _) =3D generics.split_for_impl(); + let mut generics =3D generics.clone(); + generics.params.insert(0, parse_quote!('__pin)); + let (_, ty_gen, whr) =3D generics.split_for_impl(); + let projection =3D format_ident!("{ident}Projection"); + let this =3D format_ident!("this"); + + let (fields_decl, fields_proj) =3D collect_tuple(fields.iter().map( + |f @ Field { + vis, + ident, + ty, + attrs, + .. + }| { + let mut attrs =3D attrs.clone(); + attrs.retain(|a| !a.path().is_ident("pin")); + let mut no_doc_attrs =3D attrs.clone(); + no_doc_attrs.retain(|a| !a.path().is_ident("doc")); + let ident =3D ident + .as_ref() + .expect("only structs with named fields are supported"); + if is_field_structurally_pinned(f) { + ( + quote!( + #(#attrs)* + #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + ), + quote!( + #(#no_doc_attrs)* + // SAFETY: this field is structurally pinned. + #ident: unsafe { ::core::pin::Pin::new_unchecked(&= mut #this.#ident) }, + ), + ) } else { - replace_self_and_deny_type_defs(&struct_name, tt, &mut err= s) + ( + quote!( + #(#attrs)* + #vis #ident: &'__pin mut #ty, + ), + quote!( + #(#no_doc_attrs)* + #ident: &mut #this.#ident, + ), + ) } - }) - .collect::>(); - // This should be the body of the struct `{...}`. - let last =3D rest.pop(); - let mut quoted =3D quote!(::pin_init::__pin_data! { - parse_input: - @args(#args), - @sig(#(#rest)*), - @impl_generics(#(#impl_generics)*), - @ty_generics(#(#ty_generics)*), - @decl_generics(#(#decl_generics)*), - @body(#last), - }); - quoted.extend(errs); - quoted + }, + )); + let structurally_pinned_fields_docs =3D fields + .iter() + .filter(|f| is_field_structurally_pinned(f)) + .map(|Field { ident, .. }| { + let doc =3D format!(" - `{}`", ident.as_ref().expect("")); + quote!(#[doc =3D #doc]) + }); + let not_structurally_pinned_fields_docs =3D fields + .iter() + .filter(|f| !is_field_structurally_pinned(f)) + .map(|Field { ident, .. }| { + let doc =3D format!(" - `{}`", ident.as_ref().expect("")); + quote!(#[doc =3D #doc]) + }); + let docs =3D format!(" Pin-projections of [`{ident}`]"); + quote! { + #[doc =3D #docs] + #[allow(dead_code)] + #[doc(hidden)] + #vis struct #projection #generics { + #(#fields_decl)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut (= )>, + } + + impl #og_impl_gen #ident #og_ty_gen + #whr + { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + #(#structurally_pinned_fields_docs)* + /// + /// These fields are **not** structurally pinned: + #(#not_structurally_pinned_fields_docs)* + #[inline] + #vis fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> #projection #ty_gen { + // SAFETY: we only give access to `&mut` for fields not st= ructurally pinned. + let #this =3D unsafe { ::core::pin::Pin::get_unchecked_mut= (self) }; + #projection { + #(#fields_proj)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + } + } + } } =20 -/// Replaces `Self` with `struct_name` and errors on `enum`, `trait`, `str= uct` `union` and `impl` -/// keywords. -/// -/// The error is appended to `errs` to allow normal parsing to continue. -fn replace_self_and_deny_type_defs( - struct_name: &Vec, - tt: TokenTree, - errs: &mut TokenStream, -) -> Vec { - match tt { - TokenTree::Ident(ref i) - if i =3D=3D "enum" || i =3D=3D "trait" || i =3D=3D "struct" ||= i =3D=3D "union" || i =3D=3D "impl" =3D> +fn generate_the_pin_data( + ItemStruct { + generics, + fields, + ident, + vis, + .. + }: &syn::ItemStruct, +) -> TokenStream { + let (impl_generics, ty_generics, whr) =3D generics.split_for_impl(); + + // For every field, we create an initializing projection function acco= rding to its projection + // type. If a field is structurally pinned, then it must be initialize= d via `PinInit`, if it is + // not structurally pinned, then it can be initialized via `Init`. + // + // The functions are `unsafe` to prevent accidentally calling them. + fn handle_field( + Field { + vis, + ident, + ty, + attrs, + .. + }: &Field, + struct_ident: &Ident, + pinned: bool, + ) -> TokenStream { + let mut attrs =3D attrs.clone(); + attrs.retain(|a| !a.path().is_ident("pin")); + let ident =3D ident + .as_ref() + .expect("only structs with named fields are supported"); + let project_ident =3D format_ident!("__project_{ident}"); + let (init_ty, init_fn, project_ty, project_body, pin_safety) =3D i= f pinned { + ( + quote!(PinInit), + quote!(__pinned_init), + quote!(::core::pin::Pin<&'__slot mut #ty>), + // SAFETY: this field is structurally pinned. + quote!(unsafe { ::core::pin::Pin::new_unchecked(slot) }), + quote!( + #[doc =3D " - `slot` will not move until it is dropped= , i.e. it will be pinned."] + ), + ) + } else { + ( + quote!(Init), + quote!(__init), + quote!(&'__slot mut #ty), + quote!(slot), + quote!(), + ) + }; + let slot_safety =3D format!( + " `slot` points at the field `{ident}` inside of `{struct_iden= t}`, which is pinned.", + ); + 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 #ident( + 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 + /// + #[doc =3D #slot_safety] + #(#attrs)* + #vis unsafe fn #project_ident<'__slot>( + self, + slot: &'__slot mut #ty, + ) -> #project_ty { + #project_body + } + } + } + + let field_accessors =3D fields + .iter() + .map(|f| handle_field(f, ident, is_field_structurally_pinned(f))) + .collect::(); + quote! { + // We declare this struct which will host all of the projection fu= nction for our type. It + // will be invariant over all generic parameters which are inherit= ed from the struct. + #[doc(hidden)] + #vis struct __ThePinData #generics + #whr { - errs.extend( - format!( - "::core::compile_error!(\"Cannot use `{i}` inside of s= truct definition with \ - `#[pin_data]`.\");" - ) - .parse::() - .unwrap() - .into_iter() - .map(|mut tok| { - tok.set_span(tt.span()); - tok - }), - ); - vec![tt] - } - TokenTree::Ident(i) if i =3D=3D "Self" =3D> struct_name.clone(), - TokenTree::Literal(_) | TokenTree::Punct(_) | TokenTree::Ident(_) = =3D> vec![tt], - TokenTree::Group(g) =3D> vec![TokenTree::Group(Group::new( - g.delimiter(), - g.stream() - .into_iter() - .flat_map(|tt| replace_self_and_deny_type_defs(struct_name= , tt, errs)) - .collect(), - ))], + __phantom: ::core::marker::PhantomData< + fn(#ident #ty_generics) -> #ident #ty_generics + >, + } + + impl #impl_generics ::core::clone::Clone for __ThePinData #ty_gene= rics + #whr + { + fn clone(&self) -> Self { *self } + } + + impl #impl_generics ::core::marker::Copy for __ThePinData #ty_gene= rics + #whr + {} + + #[allow(dead_code)] // Some functions might never be used and priv= ate. + #[expect(clippy::missing_safety_doc)] + impl #impl_generics __ThePinData #ty_generics + #whr + { + #field_accessors + } + + // SAFETY: We have added the correct projection functions above to= `__ThePinData` and + // we also use the least restrictive generics possible. + unsafe impl #impl_generics ::pin_init::__internal::HasPinData for = #ident #ty_generics + #whr + { + type PinData =3D __ThePinData #ty_generics; + + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { __phantom: ::core::marker::PhantomData } + } + } + + // SAFETY: TODO + unsafe impl #impl_generics ::pin_init::__internal::PinData for __T= hePinData #ty_generics + #whr + { + type Datee =3D #ident #ty_generics; + } + } +} + +fn strip_pin_annotations(struct_: &mut syn::ItemStruct) { + for field in &mut struct_.fields { + field.attrs.retain(|a| !a.path().is_ident("pin")); + } +} + +struct SelfReplacer(PathSegment); + +impl VisitMut for SelfReplacer { + fn visit_path_mut(&mut self, i: &mut syn::Path) { + if i.is_ident("Self") { + let span =3D i.span(); + let seg =3D &self.0; + *i =3D parse_quote_spanned!(span=3D> #seg); + } else { + syn::visit_mut::visit_path_mut(self, i); + } + } + + fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) { + if seg.ident =3D=3D "Self" { + let span =3D seg.span(); + let this =3D &self.0; + *seg =3D parse_quote_spanned!(span=3D> #this); + } else { + syn::visit_mut::visit_path_segment_mut(self, seg); + } + } + + fn visit_item_mut(&mut self, _: &mut Item) { + // Do not descend into items, since items reset/change what `Self`= refers to. + } +} + +// replace with `.collect()` once MSRV is above 1.79 +fn collect_tuple(iter: impl Iterator) -> (Vec, V= ec) { + let mut res_a =3D vec![]; + let mut res_b =3D vec![]; + for (a, b) in iter { + res_a.push(a); + res_b.push(b); } + (res_a, res_b) } diff --git a/rust/pin-init/src/macros.rs b/rust/pin-init/src/macros.rs index b80c95612fd6..eea8adc5c7ad 100644 --- a/rust/pin-init/src/macros.rs +++ b/rust/pin-init/src/macros.rs @@ -503,580 +503,6 @@ #[cfg(not(kernel))] pub use ::paste::paste; =20 -/// This macro first parses the struct definition such that it separates p= inned and not pinned -/// fields. Afterwards it declares the struct and implement the `PinData` = trait safely. -#[doc(hidden)] -#[macro_export] -macro_rules! __pin_data { - // Proc-macro entry point, this is supplied by the proc-macro pre-pars= ing. - (parse_input: - @args($($pinned_drop:ident)?), - @sig( - $(#[$($struct_attr:tt)*])* - $vis:vis struct $name:ident - $(where $($whr:tt)*)? - ), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @body({ $($fields:tt)* }), - ) =3D> { - // We now use token munching to iterate through all of the fields.= While doing this we - // identify fields marked with `#[pin]`, these fields are the 'pin= ned fields'. The user - // wants these to be structurally pinned. The rest of the fields a= re the - // 'not pinned fields'. Additionally we collect all fields, since = we need them in the right - // order to declare the struct. - // - // In this call we also put some explaining comments for the param= eters. - $crate::__pin_data!(find_pinned_fields: - // Attributes on the struct itself, these will just be propaga= ted to be put onto the - // struct definition. - @struct_attrs($(#[$($struct_attr)*])*), - // The visibility of the struct. - @vis($vis), - // The name of the struct. - @name($name), - // The 'impl generics', the generics that will need to be spec= ified on the struct inside - // of an `impl<$ty_generics>` block. - @impl_generics($($impl_generics)*), - // The 'ty generics', the generics that will need to be specif= ied on the impl blocks. - @ty_generics($($ty_generics)*), - // The 'decl generics', the generics that need to be specified= on the struct - // definition. - @decl_generics($($decl_generics)*), - // The where clause of any impl block and the declaration. - @where($($($whr)*)?), - // The remaining fields tokens that need to be processed. - // We add a `,` at the end to ensure correct parsing. - @fields_munch($($fields)* ,), - // The pinned fields. - @pinned(), - // The not pinned fields. - @not_pinned(), - // All fields. - @fields(), - // The accumulator containing all attributes already parsed. - @accum(), - // Contains `yes` or `` to indicate if `#[pin]` was found on t= he current field. - @is_pinned(), - // The proc-macro argument, this should be `PinnedDrop` or ``. - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We found a PhantomPinned field, this should generally be pinned! - @fields_munch($field:ident : $($($(::)?core::)?marker::)?PhantomPi= nned, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is not pinned. - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - ::core::compile_error!(concat!( - "The field `", - stringify!($field), - "` of type `PhantomPinned` only has an effect, if it has the `= #[pin]` attribute.", - )); - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)* $($accum)* $field: ::core::marker::Phantom= Pinned,), - @not_pinned($($not_pinned)*), - @fields($($fields)* $($accum)* $field: ::core::marker::Phantom= Pinned,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration. - @fields_munch($field:ident : $type:ty, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is pinned. - @is_pinned(yes), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)* $($accum)* $field: $type,), - @not_pinned($($not_pinned)*), - @fields($($fields)* $($accum)* $field: $type,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration. - @fields_munch($field:ident : $type:ty, $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - // This field is not pinned. - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)* $($accum)* $field: $type,), - @fields($($fields)* $($accum)* $field: $type,), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We found the `#[pin]` attr. - @fields_munch(#[pin] $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - // We do not include `#[pin]` in the list of attributes, since= it is not actually an - // attribute that is defined somewhere. - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)*), - // Set this to `yes`. - @is_pinned(yes), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the field declaration with visibility, for simplicit= y we only munch the - // visibility and put it into `$accum`. - @fields_munch($fvis:vis $field:ident $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($field $($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)* $fvis), - @is_pinned($($is_pinned)?), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // Some other attribute, just put it into `$accum`. - @fields_munch(#[$($attr:tt)*] $($rest:tt)*), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum($($accum:tt)*), - @is_pinned($($is_pinned:ident)?), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - $crate::__pin_data!(find_pinned_fields: - @struct_attrs($($struct_attrs)*), - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @fields_munch($($rest)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - @fields($($fields)*), - @accum($($accum)* #[$($attr)*]), - @is_pinned($($is_pinned)?), - @pinned_drop($($pinned_drop)?), - ); - }; - (find_pinned_fields: - @struct_attrs($($struct_attrs:tt)*), - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - // We reached the end of the fields, plus an optional additional c= omma, since we added one - // before and the user is also allowed to put a trailing comma. - @fields_munch($(,)?), - @pinned($($pinned:tt)*), - @not_pinned($($not_pinned:tt)*), - @fields($($fields:tt)*), - @accum(), - @is_pinned(), - @pinned_drop($($pinned_drop:ident)?), - ) =3D> { - // Declare the struct with all fields in the correct order. - $($struct_attrs)* - $vis struct $name <$($decl_generics)*> - where $($whr)* - { - $($fields)* - } - - $crate::__pin_data!(make_pin_projections: - @vis($vis), - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @decl_generics($($decl_generics)*), - @where($($whr)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - ); - - // We put the rest into this const item, because it then will not = be accessible to anything - // outside. - const _: () =3D { - // We declare this struct which will host all of the projectio= n function for our type. - // it will be invariant over all generic parameters which are = inherited from the - // struct. - $vis struct __ThePinData<$($impl_generics)*> - where $($whr)* - { - __phantom: ::core::marker::PhantomData< - fn($name<$($ty_generics)*>) -> $name<$($ty_generics)*> - >, - } - - impl<$($impl_generics)*> ::core::clone::Clone for __ThePinData= <$($ty_generics)*> - where $($whr)* - { - fn clone(&self) -> Self { *self } - } - - impl<$($impl_generics)*> ::core::marker::Copy for __ThePinData= <$($ty_generics)*> - where $($whr)* - {} - - // Make all projection functions. - $crate::__pin_data!(make_pin_data: - @pin_data(__ThePinData), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @where($($whr)*), - @pinned($($pinned)*), - @not_pinned($($not_pinned)*), - ); - - // SAFETY: We have added the correct projection functions abov= e to `__ThePinData` and - // we also use the least restrictive generics possible. - unsafe impl<$($impl_generics)*> - $crate::__internal::HasPinData for $name<$($ty_generics)*> - where $($whr)* - { - type PinData =3D __ThePinData<$($ty_generics)*>; - - unsafe fn __pin_data() -> Self::PinData { - __ThePinData { __phantom: ::core::marker::PhantomData } - } - } - - // SAFETY: TODO. - unsafe impl<$($impl_generics)*> - $crate::__internal::PinData for __ThePinData<$($ty_generic= s)*> - where $($whr)* - { - type Datee =3D $name<$($ty_generics)*>; - } - - // This struct will be used for the unpin analysis. Since only= structurally pinned - // fields are relevant whether the struct should implement `Un= pin`. - #[allow(dead_code)] - struct __Unpin <'__pin, $($impl_generics)*> - where $($whr)* - { - __phantom_pin: ::core::marker::PhantomData &'__pin ()>, - __phantom: ::core::marker::PhantomData< - fn($name<$($ty_generics)*>) -> $name<$($ty_generics)*> - >, - // Only the pinned fields. - $($pinned)* - } - - #[doc(hidden)] - impl<'__pin, $($impl_generics)*> ::core::marker::Unpin for $na= me<$($ty_generics)*> - where - __Unpin<'__pin, $($ty_generics)*>: ::core::marker::Unpin, - $($whr)* - {} - - // We need to disallow normal `Drop` implementation, the exact= behavior depends on - // whether `PinnedDrop` was specified as the parameter. - $crate::__pin_data!(drop_prevention: - @name($name), - @impl_generics($($impl_generics)*), - @ty_generics($($ty_generics)*), - @where($($whr)*), - @pinned_drop($($pinned_drop)?), - ); - }; - }; - // When no `PinnedDrop` was specified, then we have to prevent impleme= nting drop. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop(), - ) =3D> { - // We prevent this by creating a trait that will be implemented fo= r all types implementing - // `Drop`. Additionally we will implement this trait for the struc= t leading to a conflict, - // if it also implements `Drop` - trait MustNotImplDrop {} - #[expect(drop_bounds)] - impl MustNotImplDrop for T {} - impl<$($impl_generics)*> MustNotImplDrop for $name<$($ty_generics)= *> - where $($whr)* {} - // We also take care to prevent users from writing a useless `Pinn= edDrop` implementation. - // They might implement `PinnedDrop` correctly for the struct, but= forget to give - // `PinnedDrop` as the parameter to `#[pin_data]`. - #[expect(non_camel_case_types)] - trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} - impl - UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} - impl<$($impl_generics)*> - UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for $name= <$($ty_generics)*> - where $($whr)* {} - }; - // When `PinnedDrop` was specified we just implement `Drop` and delega= te. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop(PinnedDrop), - ) =3D> { - impl<$($impl_generics)*> ::core::ops::Drop for $name<$($ty_generic= s)*> - where $($whr)* - { - fn drop(&mut self) { - // SAFETY: Since this is a destructor, `self` will not mov= e after this function - // terminates, since it is inaccessible. - let pinned =3D unsafe { ::core::pin::Pin::new_unchecked(se= lf) }; - // SAFETY: Since this is a drop function, we can create th= is token to call the - // pinned destructor of this type. - let token =3D unsafe { $crate::__internal::OnlyCallFromDro= p::new() }; - $crate::PinnedDrop::drop(pinned, token); - } - } - }; - // If some other parameter was specified, we emit a readable error. - (drop_prevention: - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned_drop($($rest:tt)*), - ) =3D> { - compile_error!( - "Wrong parameters to `#[pin_data]`, expected nothing or `Pinne= dDrop`, got '{}'.", - stringify!($($rest)*), - ); - }; - (make_pin_projections: - @vis($vis:vis), - @name($name:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @decl_generics($($decl_generics:tt)*), - @where($($whr:tt)*), - @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type= :ty),* $(,)?), - @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:t= y),* $(,)?), - ) =3D> { - $crate::macros::paste! { - #[doc(hidden)] - $vis struct [< $name Projection >] <'__pin, $($decl_generics)*= > { - $($(#[$($p_attr)*])* $pvis $p_field : ::core::pin::Pin<&'_= _pin mut $p_type>,)* - $($(#[$($attr)*])* $fvis $field : &'__pin mut $type,)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin m= ut ()>, - } - - impl<$($impl_generics)*> $name<$($ty_generics)*> - where $($whr)* - { - /// Pin-projects all fields of `Self`. - /// - /// These fields are structurally pinned: - $(#[doc =3D ::core::concat!(" - `", ::core::stringify!($p_= field), "`")])* - /// - /// These fields are **not** structurally pinned: - $(#[doc =3D ::core::concat!(" - `", ::core::stringify!($fi= eld), "`")])* - #[inline] - $vis fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> [< $name Projection >] <'__pin, $($ty_generics)*> { - // SAFETY: we only give access to `&mut` for fields no= t structurally pinned. - let this =3D unsafe { ::core::pin::Pin::get_unchecked_= mut(self) }; - [< $name Projection >] { - $( - // SAFETY: `$p_field` is structurally pinned. - $(#[$($p_attr)*])* - $p_field : unsafe { ::core::pin::Pin::new_unch= ecked(&mut this.$p_field) }, - )* - $( - $(#[$($attr)*])* - $field : &mut this.$field, - )* - ___pin_phantom_data: ::core::marker::PhantomData, - } - } - } - } - }; - (make_pin_data: - @pin_data($pin_data:ident), - @impl_generics($($impl_generics:tt)*), - @ty_generics($($ty_generics:tt)*), - @where($($whr:tt)*), - @pinned($($(#[$($p_attr:tt)*])* $pvis:vis $p_field:ident : $p_type= :ty),* $(,)?), - @not_pinned($($(#[$($attr:tt)*])* $fvis:vis $field:ident : $type:t= y),* $(,)?), - ) =3D> { - $crate::macros::paste! { - // For every field, we create a projection function according = to its projection type. If a - // field is structurally pinned, then it must be initialized v= ia `PinInit`, if it is not - // structurally pinned, then it can be initialized via `Init`. - // - // The functions are `unsafe` to prevent accidentally calling = them. - #[allow(dead_code)] - #[expect(clippy::missing_safety_doc)] - impl<$($impl_generics)*> $pin_data<$($ty_generics)*> - where $($whr)* - { - $( - $(#[$($p_attr)*])* - $pvis unsafe fn $p_field( - self, - slot: *mut $p_type, - init: impl $crate::PinInit<$p_type, E>, - ) -> ::core::result::Result<(), E> { - // SAFETY: TODO. - unsafe { $crate::PinInit::__pinned_init(init, slot= ) } - } - - $(#[$($p_attr)*])* - $pvis unsafe fn [<__project_ $p_field>]<'__slot>( - self, - slot: &'__slot mut $p_type, - ) -> ::core::pin::Pin<&'__slot mut $p_type> { - ::core::pin::Pin::new_unchecked(slot) - } - )* - $( - $(#[$($attr)*])* - $fvis unsafe fn $field( - self, - slot: *mut $type, - init: impl $crate::Init<$type, E>, - ) -> ::core::result::Result<(), E> { - // SAFETY: TODO. - unsafe { $crate::Init::__init(init, slot) } - } - - $(#[$($attr)*])* - $fvis unsafe fn [<__project_ $field>]<'__slot>( - self, - slot: &'__slot mut $type, - ) -> &'__slot mut $type { - slot - } - )* - } - } - }; -} - /// The internal init macro. Do not call manually! /// /// This is called by the `{try_}{pin_}init!` macros with various inputs. --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 275424792C5; Thu, 8 Jan 2026 13:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880389; cv=none; b=MKA+MTBnka6qeej+8LorYofvIDyr09IG1Q2ZDiqZ668oDe2v2OJb6VbtRNv6L8+6Xf6sXHyPmUOi8le1UmT1P89itQSTrTO9/xFeKkTwJGvl8EPk6JaVrlQbFzWA8/UROuuaaEJL6eaWnBjVaZcYtXzgaiDUNLrE0I87n08RHSU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880389; c=relaxed/simple; bh=XPbyiK5L+U8qJiBdlhkgjy1TX6a+liT601Q+FVVH5H0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tp/7bbe2XWxOOTJhkTkRokxmBnMZOwGGJ2l0nWxFRMONcA4hjpqjnEONPr6ska+elvntlrFqXTGUiCyqcFCmogAnGjJpny2Rqeb/bkcg2TNmWLKxxXhC+UXzt40lcyr0CNF3iYL0vkI/63ddU2gbvld6XEbcvbA0LkpZyAlNnHM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=kmCbuteM; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="kmCbuteM" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7DF0FC116C6; Thu, 8 Jan 2026 13:53:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880389; bh=XPbyiK5L+U8qJiBdlhkgjy1TX6a+liT601Q+FVVH5H0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kmCbuteMZcTWnEf84lus3wr4OJxQWrcfSm2V3RrEPR6Mp79yZ4bbvAPHGGPS1//vX ef5Mdc23MmfdEu1mi3p2aWqQ5qWJJ/bWq4WFyz3kvscmZ0htjG/OK5kwruUxfchbWG 1mbe5A4gPBi87Q2vNykfVg7xExKL3xWUSHcSM+ITsN56OigLbIiM/1ZeSTQdFFmrtd MIlZrCk1AGzJcqmLsp+9CowZmSCApra60UMc1Jq9OMgOMEtEikEKH6nWi/cx3qUWtB qrsEp5imd7qpolBEUbvNYupHEyCyzJWj1WaW8JVVKRLVta9SKtkuF5qy8GDnSrZn4R AnHO5ZLogkPHQ== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 07/12] rust: pin-init: add `?Sized` bounds to traits in `#[pin_data]` macro Date: Thu, 8 Jan 2026 14:50:45 +0100 Message-ID: <20260108135127.3153925-8-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The `#[pin_data]` macro uses some auxiliary traits to ensure that a user does not implement `Drop` for the annotated struct, as that is unsound and can lead to UB. However, if the struct that is annotated is `!Sized`, the current bounds do not work, because `Sized` is an implicit bound for generics. This is *not* a soundness hole of pin-init, as it currently is impossible to construct an unsized struct using pin-init. Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/pin_data.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/interna= l/src/pin_data.rs index d1e7ed121860..0fc0ce5fd4e4 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -236,7 +236,7 @@ fn drop(&mut self) { // if it also implements `Drop` trait MustNotImplDrop {} #[expect(drop_bounds)] - impl MustNotImplDrop for T {} + impl MustNotImp= lDrop for T {} impl #impl_generics MustNotImplDrop for #ident #ty_generics #whr {} @@ -245,7 +245,7 @@ impl #impl_generics MustNotImplDrop for #ident #ty_gene= rics // `PinnedDrop` as the parameter to `#[pin_data]`. #[expect(non_camel_case_types)] trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} - impl + impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T= {} impl #impl_generics UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #= ident #ty_generics --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2913D47DD7B; Thu, 8 Jan 2026 13:53:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880399; cv=none; b=SBtiMmHKKwCsyISLdD+tJ7qd3NutJ6qMhhBAZ1Ngkwzm71JaoMBnmg4U3XXX+/8ZjbT6Pl229sSPi/saKWD6b4kLPJ1eNaWYh+xFM3kqZlcDDFaD47FNykGtFyM9uw2SAAJnKE5wXqpJg2q6tk1BoC8oceGdjmkaWigymV72WNs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880399; c=relaxed/simple; bh=VEpmBENh/rUkcbsYx6ewL93CLZSlTTuasbSN5JtpoVw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MBBWwbdnI2ZCnnsimLbpyBb58w4KIPq0iXaVLdFT1SuWeLLxMWfLrtRhgqxqL0aUCrTY82LQsyNeeLUvp/24wlUy9q7B6fWEbkO7QG0qgorRCLKsGvWdVzAh2amIM6DTvfmGasvJWKleC8L2bXkOqK5fcLIO2P8q1E/9RnEm7Wg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fq/x2AF5; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fq/x2AF5" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6D19EC116C6; Thu, 8 Jan 2026 13:53:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880398; bh=VEpmBENh/rUkcbsYx6ewL93CLZSlTTuasbSN5JtpoVw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fq/x2AF54FjkE97ePXRvz3Cmw1EIMlIgMM7PbJGl6t/mjC1anKpiMPqDIT989cPD6 Kea7ACp0cLKgG1hn8rhMKAG+PC9KQqmrhnu3YfiZavF+fLO48QCIJkqYKK+P/xZz1u wxrLxtfOlezAdAFpJWmeS4Q2745prFDuX/ygWXSU3ncW37mXxiVc9zCBGkQHnH1TyG U71JlSaXSWPD4FYSqsP5X+3o141ZcsY/mq1rS44434y5MB6l1Kzmbmbx7HKimKbRpG Xgwo05of46NX7QjsGpSrt1WMEpivq6k6RvHAU0RwSg0KJxkvnAyFsO3krpoO1Jsqee 2hrNaVEXMBJow== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Fiona Behrens , Christian Schrefl , Alban Kurti Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org Subject: [PATCH 08/12] rust: pin-init: rewrite the initializer macros using `syn` Date: Thu, 8 Jan 2026 14:50:46 +0100 Message-ID: <20260108135127.3153925-9-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Rewrite the initializer macros `[pin_]init!` using `syn`. No functional changes intended aside from improved error messages on syntactic and semantical errors. For example if one forgets to use `<-` with an initializer (and instead uses `:`): impl Bar { fn new() -> impl PinInit { ... } } impl Foo { fn new() -> impl PinInit { pin_init!(Self { bar: Bar::new() }) } } Then the declarative macro would report: error[E0308]: mismatched types --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:9 | 14 | fn new() -> impl PinInit { | ------------------ the found opaque type ... 21 | pin_init!(Self { bar: Bar::new() }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | expected `Bar`, found opaque type | arguments to this function are incorrect | =3D note: expected struct `Bar` found opaque type `impl pin_init::PinInit` note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ =3D note: this error originates in the macro `$crate::__init_interna= l` which comes from the expansion of the macro `pin_init` (in Nightly build= s, run with -Z macro-backtrace for more info) And the new error is: error[E0308]: mismatched types --> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:31 | 14 | fn new() -> impl PinInit { | ------------------ the found opaque type ... 21 | pin_init!(Self { bar: Bar::new() }) | --- ^^^^^^^^^^ expected `Bar`, found opa= que type | | | arguments to this function are incorrect | =3D note: expected struct `Bar` found opaque type `impl pin_init::PinInit` note: function defined here --> $RUST/core/src/ptr/mod.rs | | pub const unsafe fn write(dst: *mut T, src: T) { | ^^^^^ Importantly, this error gives much more accurate span locations, pointing to the offending field, rather than the entire macro invocation. Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/init.rs | 437 +++++++++++++ rust/pin-init/internal/src/lib.rs | 21 + rust/pin-init/src/lib.rs | 56 +- rust/pin-init/src/macros.rs | 951 ----------------------------- 4 files changed, 460 insertions(+), 1005 deletions(-) create mode 100644 rust/pin-init/internal/src/init.rs delete mode 100644 rust/pin-init/src/macros.rs diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/sr= c/init.rs new file mode 100644 index 000000000000..c02a99692980 --- /dev/null +++ b/rust/pin-init/internal/src/init.rs @@ -0,0 +1,437 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + braced, + parse::{End, Parse}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, +}; + +pub struct Initializer { + this: Option, + path: Path, + brace_token: token::Brace, + fields: Punctuated, + rest: Option<(Token![..], Expr)>, + error: Option<(Token![?], Type)>, +} + +struct This { + _and_token: Token![&], + ident: Ident, + _in_token: Token![in], +} + +enum InitializerField { + Value { + ident: Ident, + value: Option<(Token![:], Expr)>, + }, + Init { + ident: Ident, + _left_arrow_token: Token![<-], + value: Expr, + }, + Code { + _underscore_token: Token![_], + _colon_token: Token![:], + block: Block, + }, +} + +impl InitializerField { + fn ident(&self) -> Option<&Ident> { + match self { + Self::Value { ident, .. } | Self::Init { ident, .. } =3D> Some= (ident), + Self::Code { .. } =3D> None, + } + } +} + +pub(crate) fn expand( + Initializer { + this, + path, + brace_token, + fields, + rest, + mut error, + }: Initializer, + default_error: Option<&'static str>, + pinned: bool, +) -> TokenStream { + let mut errors =3D TokenStream::new(); + if let Some(default_error) =3D default_error { + error.get_or_insert((Default::default(), syn::parse_str(default_er= ror).unwrap())); + } + let error =3D error.map(|(_, err)| err).unwrap_or_else(|| { + errors.extend(quote_spanned!(brace_token.span.close()=3D> + ::core::compile_error!("expected `? ` after `}`"); + )); + parse_quote!(::core::convert::Infallible) + }); + let slot =3D format_ident!("slot"); + let (has_data_trait, data_trait, get_data, init_from_closure) =3D if p= inned { + ( + format_ident!("HasPinData"), + format_ident!("PinData"), + format_ident!("__pin_data"), + format_ident!("pin_init_from_closure"), + ) + } else { + ( + format_ident!("HasInitData"), + format_ident!("InitData"), + format_ident!("__init_data"), + format_ident!("init_from_closure"), + ) + }; + let init_kind =3D get_init_kind(rest, &mut errors); + let zeroable_check =3D match init_kind { + InitKind::Normal =3D> quote!(), + InitKind::Zeroing =3D> quote! { + // The user specified `..Zeroable::zeroed()` at the end of the= list of fields. + // Therefore we check if the struct implements `Zeroable` and = then zero the memory. + // This allows us to also remove the check that all fields are= present (since we + // already set the memory to zero and that is a valid bit patt= ern). + fn assert_zeroable(_: *mut T) + where T: ::pin_init::Zeroable + {} + // Ensure that the struct is indeed `Zeroable`. + assert_zeroable(#slot); + // SAFETY: The type implements `Zeroable` by the check above. + unsafe { ::core::ptr::write_bytes(#slot, 0, 1) }; + }, + }; + let this =3D match this { + None =3D> quote!(), + Some(This { ident, .. }) =3D> quote! { + // Create the `this` so it can be referenced by the user insid= e of the + // expressions creating the individual fields. + let #ident =3D unsafe { ::core::ptr::NonNull::new_unchecked(sl= ot) }; + }, + }; + // `mixed_site` ensures that the data is not accessible to the user-co= ntrolled code. + let data =3D format_ident!("__data", span =3D Span::mixed_site()); + let init_fields =3D init_fields(&fields, pinned, &data, &slot); + let field_check =3D make_field_check(&fields, init_kind, &path); + quote! {{ + // We do not want to allow arbitrary returns, so we declare this t= ype as the `Ok` return + // type and shadow it later when we insert the arbitrary user code= . That way there will be + // no possibility of returning without `unsafe`. + struct __InitOk; + + // Get the data about fields from the supplied type. + // SAFETY: TODO + let #data =3D unsafe { + use ::pin_init::__internal::#has_data_trait; + // Can't use `<#path as #has_data_trait>::#get_data`, since th= e user is able to omit + // generics (which need to be present with that syntax). + #path::#get_data() + }; + // Ensure that `#data` really is of type `#data` and help with typ= e inference: + let init =3D ::pin_init::__internal::#data_trait::make_closure::<_= , __InitOk, #error>( + #data, + move |slot| { + { + // Shadow the structure so it cannot be used to return= early. + struct __InitOk; + #zeroable_check + #this + #init_fields + #field_check + } + Ok(__InitOk) + } + ); + let init =3D move |slot| -> ::core::result::Result<(), #error> { + init(slot).map(|__InitOk| ()) + }; + // SAFETY: TODO + let init =3D unsafe { ::pin_init::#init_from_closure::<_, #error>(= init) }; + init + }} +} + +enum InitKind { + Normal, + Zeroing, +} + +fn get_init_kind(rest: Option<(Token![..], Expr)>, errors: &mut TokenStrea= m) -> InitKind { + let Some((dotdot, expr)) =3D rest else { + return InitKind::Normal; + }; + match &expr { + Expr::Call(ExprCall { func, args, .. }) if args.is_empty() =3D> ma= tch &**func { + Expr::Path(ExprPath { + attrs, + qself: None, + path: + Path { + leading_colon: None, + segments, + }, + }) if attrs.is_empty() + && segments.len() =3D=3D 2 + && segments[0].ident =3D=3D "Zeroable" + && segments[0].arguments.is_none() + && segments[1].ident =3D=3D "init_zeroed" + && segments[1].arguments.is_none() =3D> + { + return InitKind::Zeroing; + } + _ =3D> {} + }, + _ =3D> {} + } + let span =3D quote!(#dotdot #expr).span(); + errors.extend(quote_spanned!(span=3D> + ::core::compile_error!("expected nothing or `..Zeroable::init_zero= ed()`."); + )); + InitKind::Normal +} + +/// Generate the code that initializes the fields of the struct using the = initializers in `field`. +fn init_fields( + fields: &Punctuated, + pinned: bool, + data: &Ident, + slot: &Ident, +) -> TokenStream { + let mut guards =3D vec![]; + let mut res =3D TokenStream::new(); + for field in fields { + let init =3D match field { + InitializerField::Value { ident, value } =3D> { + let mut value_ident =3D ident.clone(); + let value_prep =3D value.as_ref().map(|value| &value.1).ma= p(|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 =3D #value;) + }); + // Again span for better diagnostics + let write =3D quote_spanned!(ident.span()=3D> ::core::ptr:= :write); + let accessor =3D if pinned { + let project_ident =3D format_ident!("__project_{ident}= "); + quote! { + // SAFETY: TODO + unsafe { #data.#project_ident(&mut (*#slot).#ident= ) } + } + } else { + quote! { + // SAFETY: TODO + unsafe { &mut (*#slot).#ident } + } + }; + quote! { + { + #value_prep + // SAFETY: TODO + unsafe { #write(::core::ptr::addr_of_mut!((*#slot)= .#ident), #value_ident) }; + } + #[allow(unused_variables)] + let #ident =3D #accessor; + } + } + InitializerField::Init { ident, value, .. } =3D> { + // Again span for better diagnostics + let init =3D format_ident!("init", span =3D value.span()); + if pinned { + let project_ident =3D format_ident!("__project_{ident}= "); + quote! { + { + let #init =3D #value; + // 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 correc= t trait (`Init` or `PinInit`) + // for `#ident`. + unsafe { #data.#ident(::core::ptr::addr_of_mut= !((*#slot).#ident), #init)? }; + } + // SAFETY: TODO + #[allow(unused_variables)] + let #ident =3D unsafe { #data.#project_ident(&mut = (*#slot).#ident) }; + } + } else { + quote! { + { + let #init =3D #value; + // SAFETY: `slot` is valid, because we are ins= ide of an initializer + // closure, we return when an error/panic occu= rs. + unsafe { + ::pin_init::Init::__init( + #init, + ::core::ptr::addr_of_mut!((*#slot).#id= ent), + )? + }; + } + // SAFETY: TODO + #[allow(unused_variables)] + let #ident =3D unsafe { &mut (*#slot).#ident }; + } + } + } + InitializerField::Code { block: value, .. } =3D> quote!(#[allo= w(unused_braces)] #value), + }; + res.extend(init); + if let Some(ident) =3D field.ident() { + // `mixed_site` ensures that the guard is not accessible to th= e user-controlled code. + let guard =3D format_ident!("__{ident}_guard", span =3D Span::= mixed_site()); + guards.push(guard.clone()); + res.extend(quote! { + // Create the drop guard: + // + // We rely on macro hygiene to make it impossible for user= s to access this local + // variable. + // SAFETY: We forget the guard later when initialization h= as succeeded. + let #guard =3D unsafe { + ::pin_init::__internal::DropGuard::new( + ::core::ptr::addr_of_mut!((*slot).#ident) + ) + }; + }); + } + } + quote! { + #res + // If execution reaches this point, all fields have been initializ= ed. Therefore we can now + // dismiss the guards by forgetting them. + #(::core::mem::forget(#guards);)* + } +} + +/// Generate the check for ensuring that every field has been initialized. +fn make_field_check( + fields: &Punctuated, + init_kind: InitKind, + path: &Path, +) -> TokenStream { + let fields =3D fields.iter().filter_map(|f| f.ident()); + match init_kind { + InitKind::Normal =3D> quote! { + // We use unreachable code to ensure that all fields have been= mentioned exactly once, + // this struct initializer will still be type-checked and comp= lain with a very natural + // error message if a field is forgotten/mentioned more than o= nce. + #[allow(unreachable_code, clippy::diverging_sub_expression)] + // SAFETY: this code is never executed. + let _ =3D || unsafe { + ::core::ptr::write(slot, #path { + #( + #fields: ::core::panic!(), + )* + }) + }; + }, + InitKind::Zeroing =3D> quote! { + // We use unreachable code to ensure that all fields have been= mentioned at most once. + // Since the user specified `..Zeroable::zeroed()` at the end,= all missing fields will + // be zeroed. This struct initializer will still be type-check= ed and complain with a + // very natural error message if a field is mentioned more tha= n once, or doesn't exist. + #[allow(unreachable_code, clippy::diverging_sub_expression, un= used_assignments)] + // SAFETY: this code is never executed. + let _ =3D || unsafe { + let mut zeroed =3D ::core::mem::zeroed(); + ::core::ptr::write(slot, zeroed); + zeroed =3D ::core::mem::zeroed(); + ::core::ptr::write(slot, #path { + #( + #fields: ::core::panic!(), + )* + ..zeroed + }) + }; + }, + } +} + +impl Parse for Initializer { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let this =3D input.peek(Token![&]).then(|| input.parse()).transpos= e()?; + let path =3D input.parse()?; + let content; + let brace_token =3D braced!(content in input); + let mut fields =3D Punctuated::new(); + loop { + let lh =3D content.lookahead1(); + if lh.peek(End) || lh.peek(Token![..]) { + break; + } else if lh.peek(Ident) || lh.peek(Token![_]) { + fields.push_value(content.parse()?); + let lh =3D content.lookahead1(); + if lh.peek(End) { + break; + } else if lh.peek(Token![,]) { + fields.push_punct(content.parse()?); + } else { + return Err(lh.error()); + } + } else { + return Err(lh.error()); + } + } + let rest =3D content + .peek(Token![..]) + .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse(= )?))) + .transpose()?; + let error =3D input + .peek(Token![?]) + .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) + .transpose()?; + Ok(Self { + this, + path, + brace_token, + fields, + rest, + error, + }) + } +} + +impl Parse for This { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + _and_token: input.parse()?, + ident: input.parse()?, + _in_token: input.parse()?, + }) + } +} + +impl Parse for InitializerField { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lh =3D input.lookahead1(); + if lh.peek(Token![_]) { + Ok(Self::Code { + _underscore_token: input.parse()?, + _colon_token: input.parse()?, + block: input.parse()?, + }) + } else if lh.peek(Ident) { + let ident =3D input.parse()?; + let lh =3D input.lookahead1(); + if lh.peek(Token![<-]) { + Ok(Self::Init { + ident, + _left_arrow_token: input.parse()?, + value: input.parse()?, + }) + } else if lh.peek(Token![:]) { + Ok(Self::Value { + ident, + value: Some((input.parse()?, input.parse()?)), + }) + } else if lh.peek(Token![,]) || lh.peek(End) { + Ok(Self::Value { ident, value: None }) + } else { + Err(lh.error()) + } + } else { + Err(lh.error()) + } + } +} diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src= /lib.rs index 243684a1eedc..c7c59ae77eda 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -13,6 +13,7 @@ use proc_macro::TokenStream; use syn::parse_macro_input; =20 +mod init; mod pin_data; mod pinned_drop; mod zeroable; @@ -47,3 +48,23 @@ pub fn derive_zeroable(input: TokenStream) -> TokenStrea= m { pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { zeroable::maybe_derive(parse_macro_input!(input as _)).into() } + +#[proc_macro] +pub fn init(input: TokenStream) -> TokenStream { + init::expand( + parse_macro_input!(input as _), + Some("::core::convert::Infallible"), + false, + ) + .into() +} + +#[proc_macro] +pub fn pin_init(input: TokenStream) -> TokenStream { + init::expand( + parse_macro_input!(input as _), + Some("::core::convert::Infallible"), + true, + ) + .into() +} diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 0e707f00061f..780b4fead22d 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -297,8 +297,6 @@ =20 #[doc(hidden)] pub mod __internal; -#[doc(hidden)] -pub mod macros; =20 #[cfg(any(feature =3D "std", feature =3D "alloc"))] mod alloc; @@ -781,32 +779,7 @@ macro_rules! stack_try_pin_init { /// ``` /// /// [`NonNull`]: core::ptr::NonNull -// For a detailed example of how this macro works, see the module document= ation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) =3D> { - $crate::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? ::core::convert::Infallible) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) =3D> { - $crate::__init_internal!( - @this($($this)?), - @typ($t $(::<$($generics),*>)? ), - @fields($($fields)*), - @error($err), - @data(PinData, use_data), - @has_data(HasPinData, __pin_data), - @construct_closure(pin_init_from_closure), - @munch_fields($($fields)*), - ) - } -} +pub use pin_init_internal::pin_init; =20 /// Construct an in-place, fallible initializer for `struct`s. /// @@ -844,32 +817,7 @@ macro_rules! pin_init { /// } /// # let _ =3D Box::init(BigBuf::new()); /// ``` -// For a detailed example of how this macro works, see the module document= ation of the hidden -// module `macros` inside of `macros.rs`. -#[macro_export] -macro_rules! init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) =3D> { - $crate::init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? ::core::convert::Infallible) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) =3D> { - $crate::__init_internal!( - @this($($this)?), - @typ($t $(::<$($generics),*>)?), - @fields($($fields)*), - @error($err), - @data(InitData, /*no use_data*/), - @has_data(HasInitData, __init_data), - @construct_closure(init_from_closure), - @munch_fields($($fields)*), - ) - } -} +pub use pin_init_internal::init; =20 /// Asserts that a field on a struct using `#[pin_data]` is marked with `#= [pin]` ie. that it is /// structurally pinned. diff --git a/rust/pin-init/src/macros.rs b/rust/pin-init/src/macros.rs deleted file mode 100644 index eea8adc5c7ad..000000000000 --- a/rust/pin-init/src/macros.rs +++ /dev/null @@ -1,951 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This module provides the macros that actually implement the proc-macro= s `pin_data` and -//! `pinned_drop`. It also contains `__init_internal`, the implementation = of the -//! `{try_}{pin_}init!` macros. -//! -//! These macros should never be called directly, since they expect their = input to be -//! in a certain format which is internal. If used incorrectly, these macr= os can lead to UB even in -//! safe code! Use the public facing macros instead. -//! -//! This architecture has been chosen because the kernel does not yet have= access to `syn` which -//! would make matters a lot easier for implementing these as proc-macros. -//! -//! Since this library and the kernel implementation should diverge as lit= tle as possible, the same -//! approach has been taken here. -//! -//! # Macro expansion example -//! -//! This section is intended for readers trying to understand the macros i= n this module and the -//! `[try_][pin_]init!` macros from `lib.rs`. -//! -//! We will look at the following example: -//! -//! ```rust,ignore -//! #[pin_data] -//! #[repr(C)] -//! struct Bar { -//! #[pin] -//! t: T, -//! pub x: usize, -//! } -//! -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! pin_init!(Self { t, x: 0 }) -//! } -//! } -//! -//! #[pin_data(PinnedDrop)] -//! struct Foo { -//! a: usize, -//! #[pin] -//! b: Bar, -//! } -//! -//! #[pinned_drop] -//! impl PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! -//! let a =3D 42; -//! let initializer =3D pin_init!(Foo { -//! a, -//! b <- Bar::new(36), -//! }); -//! ``` -//! -//! This example includes the most common and important features of the pi= n-init API. -//! -//! Below you can find individual section about the different macro invoca= tions. Here are some -//! general things we need to take into account when designing macros: -//! - use global paths, similarly to file paths, these start with the sepa= rator: `::core::panic!()` -//! this ensures that the correct item is used, since users could define= their own `mod core {}` -//! and then their own `panic!` inside to execute arbitrary code inside = of our macro. -//! - macro `unsafe` hygiene: we need to ensure that we do not expand arbi= trary, user-supplied -//! expressions inside of an `unsafe` block in the macro, because this w= ould allow users to do -//! `unsafe` operations without an associated `unsafe` block. -//! -//! ## `#[pin_data]` on `Bar` -//! -//! This macro is used to specify which fields are structurally pinned and= which fields are not. It -//! is placed on the struct definition and allows `#[pin]` to be placed on= the fields. -//! -//! Here is the definition of `Bar` from our example: -//! -//! ```rust,ignore -//! #[pin_data] -//! #[repr(C)] -//! struct Bar { -//! #[pin] -//! t: T, -//! pub x: usize, -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! // Firstly the normal definition of the struct, attributes are preserv= ed: -//! #[repr(C)] -//! struct Bar { -//! t: T, -//! pub x: usize, -//! } -//! // Then an anonymous constant is defined, this is because we do not wa= nt any code to access the -//! // types that we define inside: -//! const _: () =3D { -//! // We define the pin-data carrying struct, it is a ZST and needs t= o have the same generics, -//! // since we need to implement access functions for each field and = thus need to know its -//! // type. -//! struct __ThePinData { -//! __phantom: ::core::marker::PhantomData) -> Bar>, -//! } -//! // We implement `Copy` for the pin-data struct, since all function= s it defines will take -//! // `self` by value. -//! impl ::core::clone::Clone for __ThePinData { -//! fn clone(&self) -> Self { -//! *self -//! } -//! } -//! impl ::core::marker::Copy for __ThePinData {} -//! // For every field of `Bar`, the pin-data struct will define a fun= ction with the same name -//! // and accessor (`pub` or `pub(crate)` etc.). This function will t= ake a pointer to the -//! // field (`slot`) and a `PinInit` or `Init` depending on the proje= ction kind of the field -//! // (if pinning is structural for the field, then `PinInit` otherwi= se `Init`). -//! #[allow(dead_code)] -//! impl __ThePinData { -//! unsafe fn t( -//! self, -//! slot: *mut T, -//! // Since `t` is `#[pin]`, this is `PinInit`. -//! init: impl ::pin_init::PinInit, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } -//! } -//! pub unsafe fn x( -//! self, -//! slot: *mut usize, -//! // Since `x` is not `#[pin]`, this is `Init`. -//! init: impl ::pin_init::Init, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::Init::__init(init, slot) } -//! } -//! } -//! // Implement the internal `HasPinData` trait that associates `Bar`= with the pin-data struct -//! // that we constructed above. -//! unsafe impl ::pin_init::__internal::HasPinData for Bar { -//! type PinData =3D __ThePinData; -//! unsafe fn __pin_data() -> Self::PinData { -//! __ThePinData { -//! __phantom: ::core::marker::PhantomData, -//! } -//! } -//! } -//! // Implement the internal `PinData` trait that marks the pin-data = struct as a pin-data -//! // struct. This is important to ensure that no user can implement = a rogue `__pin_data` -//! // function without using `unsafe`. -//! unsafe impl ::pin_init::__internal::PinData for __ThePinData= { -//! type Datee =3D Bar; -//! } -//! // Now we only want to implement `Unpin` for `Bar` when every stru= cturally pinned field is -//! // `Unpin`. In other words, whether `Bar` is `Unpin` only depends = on structurally pinned -//! // fields (those marked with `#[pin]`). These fields will be liste= d in this struct, in our -//! // case no such fields exist, hence this is almost empty. The two = phantomdata fields exist -//! // for two reasons: -//! // - `__phantom`: every generic must be used, since we cannot real= ly know which generics -//! // are used, we declare all and then use everything here once. -//! // - `__phantom_pin`: uses the `'__pin` lifetime and ensures that = this struct is invariant -//! // over it. The lifetime is needed to work around the limitation= that trait bounds must -//! // not be trivial, e.g. the user has a `#[pin] PhantomPinned` fi= eld -- this is -//! // unconditionally `!Unpin` and results in an error. The lifetim= e tricks the compiler -//! // into accepting these bounds regardless. -//! #[allow(dead_code)] -//! struct __Unpin<'__pin, T> { -//! __phantom_pin: ::core::marker::PhantomData &= '__pin ()>, -//! __phantom: ::core::marker::PhantomData) -> Bar>, -//! // Our only `#[pin]` field is `t`. -//! t: T, -//! } -//! #[doc(hidden)] -//! impl<'__pin, T> ::core::marker::Unpin for Bar -//! where -//! __Unpin<'__pin, T>: ::core::marker::Unpin, -//! {} -//! // Now we need to ensure that `Bar` does not implement `Drop`, sin= ce that would give users -//! // access to `&mut self` inside of `drop` even if the struct was p= inned. This could lead to -//! // UB with only safe code, so we disallow this by giving a trait i= mplementation error using -//! // a direct impl and a blanket implementation. -//! trait MustNotImplDrop {} -//! // Normally `Drop` bounds do not have the correct semantics, but f= or this purpose they do -//! // (normally people want to know if a type has any kind of drop gl= ue at all, here we want -//! // to know if it has any kind of custom drop glue, which is exactl= y what this bound does). -//! #[expect(drop_bounds)] -//! impl MustNotImplDrop for T {} -//! impl MustNotImplDrop for Bar {} -//! // Here comes a convenience check, if one implemented `PinnedDrop`= , but forgot to add it to -//! // `#[pin_data]`, then this will error with the same mechanic as a= bove, this is not needed -//! // for safety, but a good sanity check, since no normal code calls= `PinnedDrop::drop`. -//! #[expect(non_camel_case_types)] -//! trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} -//! impl< -//! T: ::pin_init::PinnedDrop, -//! > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} -//! impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for B= ar {} -//! }; -//! ``` -//! -//! ## `pin_init!` in `impl Bar` -//! -//! This macro creates an pin-initializer for the given struct. It require= s that the struct is -//! annotated by `#[pin_data]`. -//! -//! Here is the impl on `Bar` defining the new function: -//! -//! ```rust,ignore -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! pin_init!(Self { t, x: 0 }) -//! } -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! impl Bar { -//! fn new(t: T) -> impl PinInit { -//! { -//! // We do not want to allow arbitrary returns, so we declar= e this type as the `Ok` -//! // return type and shadow it later when we insert the arbi= trary user code. That way -//! // there will be no possibility of returning without `unsa= fe`. -//! struct __InitOk; -//! // Get the data about fields from the supplied type. -//! // - the function is unsafe, hence the unsafe block -//! // - we `use` the `HasPinData` trait in the block, it is o= nly available in that -//! // scope. -//! let data =3D unsafe { -//! use ::pin_init::__internal::HasPinData; -//! Self::__pin_data() -//! }; -//! // Ensure that `data` really is of type `PinData` and help= with type inference: -//! let init =3D ::pin_init::__internal::PinData::make_closure= ::< -//! _, -//! __InitOk, -//! ::core::convert::Infallible, -//! >(data, move |slot| { -//! { -//! // Shadow the structure so it cannot be used to re= turn early. If a user -//! // tries to write `return Ok(__InitOk)`, then they= get a type error, -//! // since that will refer to this struct instead of= the one defined -//! // above. -//! struct __InitOk; -//! // This is the expansion of `t,`, which is syntact= ic sugar for `t: t,`. -//! { -//! unsafe { ::core::ptr::write(::core::addr_of_mu= t!((*slot).t), t) }; -//! } -//! // Since initialization could fail later (not in t= his case, since the -//! // error type is `Infallible`) we will need to dro= p this field if there -//! // is an error later. This `DropGuard` will drop t= he field when it gets -//! // dropped and has not yet been forgotten. -//! let __t_guard =3D unsafe { -//! ::pin_init::__internal::DropGuard::new(::core:= :addr_of_mut!((*slot).t)) -//! }; -//! // Expansion of `x: 0,`: -//! // Since this can be an arbitrary expression we ca= nnot place it inside -//! // of the `unsafe` block, so we bind it here. -//! { -//! let x =3D 0; -//! unsafe { ::core::ptr::write(::core::addr_of_mu= t!((*slot).x), x) }; -//! } -//! // We again create a `DropGuard`. -//! let __x_guard =3D unsafe { -//! ::pin_init::__internal::DropGuard::new(::core:= :addr_of_mut!((*slot).x)) -//! }; -//! // Since initialization has successfully completed= , we can now forget -//! // the guards. This is not `mem::forget`, since we= only have -//! // `&DropGuard`. -//! ::core::mem::forget(__x_guard); -//! ::core::mem::forget(__t_guard); -//! // Here we use the type checker to ensure that eve= ry field has been -//! // initialized exactly once, since this is `if fal= se` it will never get -//! // executed, but still type-checked. -//! // Additionally we abuse `slot` to automatically i= nfer the correct type -//! // for the struct. This is also another check that= every field is -//! // accessible from this scope. -//! #[allow(unreachable_code, clippy::diverging_sub_ex= pression)] -//! let _ =3D || { -//! unsafe { -//! ::core::ptr::write( -//! slot, -//! Self { -//! // We only care about typecheck fi= nding every field -//! // here, the expression does not m= atter, just conjure -//! // one using `panic!()`: -//! t: ::core::panic!(), -//! x: ::core::panic!(), -//! }, -//! ); -//! }; -//! }; -//! } -//! // We leave the scope above and gain access to the pre= viously shadowed -//! // `__InitOk` that we need to return. -//! Ok(__InitOk) -//! }); -//! // Change the return type from `__InitOk` to `()`. -//! let init =3D move | -//! slot, -//! | -> ::core::result::Result<(), ::core::convert::Infallibl= e> { -//! init(slot).map(|__InitOk| ()) -//! }; -//! // Construct the initializer. -//! let init =3D unsafe { -//! ::pin_init::pin_init_from_closure::< -//! _, -//! ::core::convert::Infallible, -//! >(init) -//! }; -//! init -//! } -//! } -//! } -//! ``` -//! -//! ## `#[pin_data]` on `Foo` -//! -//! Since we already took a look at `#[pin_data]` on `Bar`, this section w= ill only explain the -//! differences/new things in the expansion of the `Foo` definition: -//! -//! ```rust,ignore -//! #[pin_data(PinnedDrop)] -//! struct Foo { -//! a: usize, -//! #[pin] -//! b: Bar, -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! struct Foo { -//! a: usize, -//! b: Bar, -//! } -//! const _: () =3D { -//! struct __ThePinData { -//! __phantom: ::core::marker::PhantomData Foo>, -//! } -//! impl ::core::clone::Clone for __ThePinData { -//! fn clone(&self) -> Self { -//! *self -//! } -//! } -//! impl ::core::marker::Copy for __ThePinData {} -//! #[allow(dead_code)] -//! impl __ThePinData { -//! unsafe fn b( -//! self, -//! slot: *mut Bar, -//! init: impl ::pin_init::PinInit, E>, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } -//! } -//! unsafe fn a( -//! self, -//! slot: *mut usize, -//! init: impl ::pin_init::Init, -//! ) -> ::core::result::Result<(), E> { -//! unsafe { ::pin_init::Init::__init(init, slot) } -//! } -//! } -//! unsafe impl ::pin_init::__internal::HasPinData for Foo { -//! type PinData =3D __ThePinData; -//! unsafe fn __pin_data() -> Self::PinData { -//! __ThePinData { -//! __phantom: ::core::marker::PhantomData, -//! } -//! } -//! } -//! unsafe impl ::pin_init::__internal::PinData for __ThePinData { -//! type Datee =3D Foo; -//! } -//! #[allow(dead_code)] -//! struct __Unpin<'__pin> { -//! __phantom_pin: ::core::marker::PhantomData &= '__pin ()>, -//! __phantom: ::core::marker::PhantomData Foo>, -//! b: Bar, -//! } -//! #[doc(hidden)] -//! impl<'__pin> ::core::marker::Unpin for Foo -//! where -//! __Unpin<'__pin>: ::core::marker::Unpin, -//! {} -//! // Since we specified `PinnedDrop` as the argument to `#[pin_data]= `, we expect `Foo` to -//! // implement `PinnedDrop`. Thus we do not need to prevent `Drop` i= mplementations like -//! // before, instead we implement `Drop` here and delegate to `Pinne= dDrop`. -//! impl ::core::ops::Drop for Foo { -//! fn drop(&mut self) { -//! // Since we are getting dropped, no one else has a referen= ce to `self` and thus we -//! // can assume that we never move. -//! let pinned =3D unsafe { ::core::pin::Pin::new_unchecked(se= lf) }; -//! // Create the unsafe token that proves that we are inside = of a destructor, this -//! // type is only allowed to be created in a destructor. -//! let token =3D unsafe { ::pin_init::__internal::OnlyCallFro= mDrop::new() }; -//! ::pin_init::PinnedDrop::drop(pinned, token); -//! } -//! } -//! }; -//! ``` -//! -//! ## `#[pinned_drop]` on `impl PinnedDrop for Foo` -//! -//! This macro is used to implement the `PinnedDrop` trait, since that tra= it is `unsafe` and has an -//! extra parameter that should not be used at all. The macro hides that p= arameter. -//! -//! Here is the `PinnedDrop` impl for `Foo`: -//! -//! ```rust,ignore -//! #[pinned_drop] -//! impl PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! // `unsafe`, full path and the token parameter are added, everything e= lse stays the same. -//! unsafe impl ::pin_init::PinnedDrop for Foo { -//! fn drop(self: Pin<&mut Self>, _: ::pin_init::__internal::OnlyCallF= romDrop) { -//! println!("{self:p} is getting dropped."); -//! } -//! } -//! ``` -//! -//! ## `pin_init!` on `Foo` -//! -//! Since we already took a look at `pin_init!` on `Bar`, this section wil= l only show the expansion -//! of `pin_init!` on `Foo`: -//! -//! ```rust,ignore -//! let a =3D 42; -//! let initializer =3D pin_init!(Foo { -//! a, -//! b <- Bar::new(36), -//! }); -//! ``` -//! -//! This expands to the following code: -//! -//! ```rust,ignore -//! let a =3D 42; -//! let initializer =3D { -//! struct __InitOk; -//! let data =3D unsafe { -//! use ::pin_init::__internal::HasPinData; -//! Foo::__pin_data() -//! }; -//! let init =3D ::pin_init::__internal::PinData::make_closure::< -//! _, -//! __InitOk, -//! ::core::convert::Infallible, -//! >(data, move |slot| { -//! { -//! struct __InitOk; -//! { -//! unsafe { ::core::ptr::write(::core::addr_of_mut!((*slo= t).a), a) }; -//! } -//! let __a_guard =3D unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of= _mut!((*slot).a)) -//! }; -//! let init =3D Bar::new(36); -//! unsafe { data.b(::core::addr_of_mut!((*slot).b), b)? }; -//! let __b_guard =3D unsafe { -//! ::pin_init::__internal::DropGuard::new(::core::addr_of= _mut!((*slot).b)) -//! }; -//! ::core::mem::forget(__b_guard); -//! ::core::mem::forget(__a_guard); -//! #[allow(unreachable_code, clippy::diverging_sub_expression= )] -//! let _ =3D || { -//! unsafe { -//! ::core::ptr::write( -//! slot, -//! Foo { -//! a: ::core::panic!(), -//! b: ::core::panic!(), -//! }, -//! ); -//! }; -//! }; -//! } -//! Ok(__InitOk) -//! }); -//! let init =3D move | -//! slot, -//! | -> ::core::result::Result<(), ::core::convert::Infallible> { -//! init(slot).map(|__InitOk| ()) -//! }; -//! let init =3D unsafe { -//! ::pin_init::pin_init_from_closure::<_, ::core::convert::Infall= ible>(init) -//! }; -//! init -//! }; -//! ``` - -#[cfg(kernel)] -pub use ::macros::paste; -#[cfg(not(kernel))] -pub use ::paste::paste; - -/// The internal init macro. Do not call manually! -/// -/// This is called by the `{try_}{pin_}init!` macros with various inputs. -/// -/// This macro has multiple internal call configurations, these are always= the very first ident: -/// - nothing: this is the base case and called by the `{try_}{pin_}init!`= macros. -/// - `with_update_parsed`: when the `..Zeroable::init_zeroed()` syntax ha= s been handled. -/// - `init_slot`: recursively creates the code that initializes all field= s in `slot`. -/// - `make_initializer`: recursively create the struct initializer that g= uarantees that every -/// field has been initialized exactly once. -#[doc(hidden)] -#[macro_export] -macro_rules! __init_internal { - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be pres= ent in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields(), - ) =3D> { - $crate::__init_internal!(with_update_parsed: - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @init_zeroed(), // Nothing means default behavior. - ) - }; - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be pres= ent in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields(..Zeroable::init_zeroed()), - ) =3D> { - $crate::__init_internal!(with_update_parsed: - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @init_zeroed(()), // `()` means zero all fields not mentioned. - ) - }; - ( - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be pres= ent in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @munch_fields($ignore:tt $($rest:tt)*), - ) =3D> { - $crate::__init_internal!( - @this($($this)?), - @typ($t), - @fields($($fields)*), - @error($err), - @data($data, $($use_data)?), - @has_data($has_data, $get_data), - @construct_closure($construct_closure), - @munch_fields($($rest)*), - ) - }; - (with_update_parsed: - @this($($this:ident)?), - @typ($t:path), - @fields($($fields:tt)*), - @error($err:ty), - // Either `PinData` or `InitData`, `$use_data` should only be pres= ent in the `PinData` - // case. - @data($data:ident, $($use_data:ident)?), - // `HasPinData` or `HasInitData`. - @has_data($has_data:ident, $get_data:ident), - // `pin_init_from_closure` or `init_from_closure`. - @construct_closure($construct_closure:ident), - @init_zeroed($($init_zeroed:expr)?), - ) =3D> {{ - // We do not want to allow arbitrary returns, so we declare this t= ype as the `Ok` return - // type and shadow it later when we insert the arbitrary user code= . That way there will be - // no possibility of returning without `unsafe`. - struct __InitOk; - // Get the data about fields from the supplied type. - // - // SAFETY: TODO. - let data =3D unsafe { - use $crate::__internal::$has_data; - // Here we abuse `paste!` to retokenize `$t`. Declarative macr= os have some internal - // information that is associated to already parsed fragments,= so a path fragment - // cannot be used in this position. Doing the retokenization r= esults in valid rust - // code. - $crate::macros::paste!($t::$get_data()) - }; - // Ensure that `data` really is of type `$data` and help with type= inference: - let init =3D $crate::__internal::$data::make_closure::<_, __InitOk= , $err>( - data, - move |slot| { - { - // Shadow the structure so it cannot be used to return= early. - struct __InitOk; - // If `$init_zeroed` is present we should zero the slo= t now and not emit an - // error when fields are missing (since they will be z= eroed). We also have to - // check that the type actually implements `Zeroable`. - $({ - fn assert_zeroable(_: *mut T)= {} - // Ensure that the struct is indeed `Zeroable`. - assert_zeroable(slot); - // SAFETY: The type implements `Zeroable` by the c= heck above. - unsafe { ::core::ptr::write_bytes(slot, 0, 1) }; - $init_zeroed // This will be `()` if set. - })? - // Create the `this` so it can be referenced by the us= er inside of the - // expressions creating the individual fields. - $(let $this =3D unsafe { ::core::ptr::NonNull::new_unc= hecked(slot) };)? - // Initialize every field. - $crate::__init_internal!(init_slot($($use_data)?): - @data(data), - @slot(slot), - @guards(), - @munch_fields($($fields)*,), - ); - // We use unreachable code to ensure that all fields h= ave been mentioned exactly - // once, this struct initializer will still be type-ch= ecked and complain with a - // very natural error message if a field is forgotten/= mentioned more than once. - #[allow(unreachable_code, clippy::diverging_sub_expres= sion)] - let _ =3D || { - $crate::__init_internal!(make_initializer: - @slot(slot), - @type_name($t), - @munch_fields($($fields)*,), - @acc(), - ); - }; - } - Ok(__InitOk) - } - ); - let init =3D move |slot| -> ::core::result::Result<(), $err> { - init(slot).map(|__InitOk| ()) - }; - // SAFETY: TODO. - let init =3D unsafe { $crate::$construct_closure::<_, $err>(init) = }; - init - }}; - (init_slot($($use_data:ident)?): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - @munch_fields($(..Zeroable::init_zeroed())? $(,)?), - ) =3D> { - // Endpoint of munching, no fields are left. If execution reaches = this point, all fields - // have been initialized. Therefore we can now dismiss the guards = by forgetting them. - $(::core::mem::forget($guards);)* - }; - (init_slot($($use_data:ident)?): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // arbitrary code block - @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), - ) =3D> { - { $($code)* } - $crate::__init_internal!(init_slot($($use_data)?): - @data($data), - @slot($slot), - @guards($($guards,)*), - @munch_fields($($rest)*), - ); - }; - (init_slot($use_data:ident): // `use_data` is present, so we use the `= data` to init fields. - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // In-place initialization syntax. - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - ) =3D> { - let init =3D $val; - // Call the initializer. - // - // SAFETY: `slot` is valid, because we are inside of an initialize= r closure, we - // return when an error/panic occurs. - // We also use the `data` to require the correct trait (`Init` or = `PinInit`) for `$field`. - unsafe { $data.$field(::core::ptr::addr_of_mut!((*$slot).$field), = init)? }; - // SAFETY: - // - the project function does the correct field projection, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field =3D $crate::macros::paste!(unsafe { $data.[< __project_= $field >](&mut (*$slot).$field) }); - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to acc= ess this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has s= ucceeded. - let [< __ $field _guard >] =3D unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mu= t!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot($use_data): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot(): // No `use_data`, so we use `Init::__init` directly. - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // In-place initialization syntax. - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - ) =3D> { - let init =3D $val; - // Call the initializer. - // - // SAFETY: `slot` is valid, because we are inside of an initialize= r closure, we - // return when an error/panic occurs. - unsafe { $crate::Init::__init(init, ::core::ptr::addr_of_mut!((*$s= lot).$field))? }; - - // SAFETY: - // - the field is not structurally pinned, since the line above mu= st compile, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field =3D unsafe { &mut (*$slot).$field }; - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to acc= ess this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has s= ucceeded. - let [< __ $field _guard >] =3D unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mu= t!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot(): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot(): // No `use_data`, so all fields are not structurally pin= ned - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // Init by-value. - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - ) =3D> { - { - $(let $field =3D $val;)? - // Initialize the field. - // - // SAFETY: The memory at `slot` is uninitialized. - unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot)= .$field), $field) }; - } - - #[allow(unused_variables)] - // SAFETY: - // - the field is not structurally pinned, since no `use_data` was= required to create this - // initializer, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - let $field =3D unsafe { &mut (*$slot).$field }; - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to acc= ess this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has s= ucceeded. - let [< __ $field _guard >] =3D unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mu= t!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot(): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (init_slot($use_data:ident): - @data($data:ident), - @slot($slot:ident), - @guards($($guards:ident,)*), - // Init by-value. - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - ) =3D> { - { - $(let $field =3D $val;)? - // Initialize the field. - // - // SAFETY: The memory at `slot` is uninitialized. - unsafe { ::core::ptr::write(::core::ptr::addr_of_mut!((*$slot)= .$field), $field) }; - } - // SAFETY: - // - the project function does the correct field projection, - // - the field has been initialized, - // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field =3D $crate::macros::paste!(unsafe { $data.[< __project_= $field >](&mut (*$slot).$field) }); - - // Create the drop guard: - // - // We rely on macro hygiene to make it impossible for users to acc= ess this local variable. - // We use `paste!` to create new hygiene for `$field`. - $crate::macros::paste! { - // SAFETY: We forget the guard later when initialization has s= ucceeded. - let [< __ $field _guard >] =3D unsafe { - $crate::__internal::DropGuard::new(::core::ptr::addr_of_mu= t!((*$slot).$field)) - }; - - $crate::__init_internal!(init_slot($use_data): - @data($data), - @slot($slot), - @guards([< __ $field _guard >], $($guards,)*), - @munch_fields($($rest)*), - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields(_: { $($code:tt)* }, $($rest:tt)*), - @acc($($acc:tt)*), - ) =3D> { - // code blocks are ignored for the initializer check - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)*), - ); - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields(..Zeroable::init_zeroed() $(,)?), - @acc($($acc:tt)*), - ) =3D> { - // Endpoint, nothing more to munch, create the initializer. Since = the users specified - // `..Zeroable::init_zeroed()`, the slot will already have been ze= roed and all field that have - // not been overwritten are thus zero and initialized. We still ch= eck that all fields are - // actually accessible by using the struct update syntax ourselves. - // We are inside of a closure that is never executed and thus we c= an abuse `slot` to - // get the correct type inference here: - #[allow(unused_assignments)] - unsafe { - let mut zeroed =3D ::core::mem::zeroed(); - // We have to use type inference here to make zeroed have the = correct type. This does - // not get executed, so it has no effect. - ::core::ptr::write($slot, zeroed); - zeroed =3D ::core::mem::zeroed(); - // Here we abuse `paste!` to retokenize `$t`. Declarative macr= os have some internal - // information that is associated to already parsed fragments,= so a path fragment - // cannot be used in this position. Doing the retokenization r= esults in valid rust - // code. - $crate::macros::paste!( - ::core::ptr::write($slot, $t { - $($acc)* - ..zeroed - }); - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($(,)?), - @acc($($acc:tt)*), - ) =3D> { - // Endpoint, nothing more to munch, create the initializer. - // Since we are in the closure that is never called, this will nev= er get executed. - // We abuse `slot` to get the correct type inference here: - // - // SAFETY: TODO. - unsafe { - // Here we abuse `paste!` to retokenize `$t`. Declarative macr= os have some internal - // information that is associated to already parsed fragments,= so a path fragment - // cannot be used in this position. Doing the retokenization r= esults in valid rust - // code. - $crate::macros::paste!( - ::core::ptr::write($slot, $t { - $($acc)* - }); - ); - } - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($field:ident <- $val:expr, $($rest:tt)*), - @acc($($acc:tt)*), - ) =3D> { - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)* $field: ::core::panic!(),), - ); - }; - (make_initializer: - @slot($slot:ident), - @type_name($t:path), - @munch_fields($field:ident $(: $val:expr)?, $($rest:tt)*), - @acc($($acc:tt)*), - ) =3D> { - $crate::__init_internal!(make_initializer: - @slot($slot), - @type_name($t), - @munch_fields($($rest)*), - @acc($($acc)* $field: ::core::panic!(),), - ); - }; -} --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C1A6E466B6B; Thu, 8 Jan 2026 13:53:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880404; cv=none; b=flzS0+fTS0tfJML26fz0vg33SzaxsT2NAzYxNqcKDF1ZqyXs/V1xcbFjkCWVclXF8FwehEYqItcxUuNGAwEs9xdLck4S8mGadaMwuARaQYmYSefVsrB4NQK/p0m1MbX8sW/OthLIkaAtbyAoDcXwOBDZxT2HyL8NOjUKX5z1p3M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880404; c=relaxed/simple; bh=Bv5KxUkIfgGHqKdxc6lTLVWWeBXrVJcoaQUm+djAo0Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GWiqg0WgTIwsak7jHj83ZRqFCUrYIioM0+m8wwLVk5YTcroV2wMvdbIAxXAkGYSU4T2J7QyEM+/pErAYVS1bSfh6xmSNvZMoukjobtIOEiLbyvQk5hinoYSrjBeXK5JpJGzKT95dOVywd5UlUYVnaIB2GZlPVDpP6zqxYOEj3eg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lTMv4iwE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lTMv4iwE" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E1BBBC116C6; Thu, 8 Jan 2026 13:53:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880404; bh=Bv5KxUkIfgGHqKdxc6lTLVWWeBXrVJcoaQUm+djAo0Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lTMv4iwE2Tk9UPaq776a7jl89kGGaJ5fD29/C0VLigZjKM2JSAGzxF/x9vqn+Nq2k css+PfQDzPdEGfYlxtn7WKZwqJJKEVIhbqJJZaDM99VBGHb/aDGeBoUSEoRov7KUZC 7pOTSYGjVQ1V39/MeFNAVdfPsKUjhEf0ttqTzpjCXnMRFjQY3Ggj444/ZCDDh7p+xR wtrfahvJ0jrkam2pWaGF6J3n1DCEck0215/1viKio2hMjepI6x4XrYfEKT1RfRksG0 Ujk3CZ7WgPSL3UKuEx4uCj0CvgpexAHrxPuBOxkrokWR4tvI2acWntT8n4ULWGNOxq BuWDyXBikwnww== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 09/12] rust: pin-init: add `#[default_error()]` attribute to initializer macros Date: Thu, 8 Jan 2026 14:50:47 +0100 Message-ID: <20260108135127.3153925-10-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The `#[default_error()]` attribute can be used to supply a default type as the error used for the `[pin_]init!` macros. This way one can easily define custom `try_[pin_]init!` variants that default to your project specific error type. Just write the following declarative macro: macro_rules! try_init { ($($args:tt)*) =3D> { ::pin_init::init!( #[default_error(YourCustomErrorType)] $($args)* ) } } Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/init.rs | 53 +++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/sr= c/init.rs index c02a99692980..e14bacc88f41 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -6,10 +6,11 @@ parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token,= Type, }; =20 pub struct Initializer { + attrs: Vec, this: Option, path: Path, brace_token: token::Brace, @@ -50,23 +51,44 @@ fn ident(&self) -> Option<&Ident> { } } =20 +enum InitializerAttribute { + DefaultError(DefaultErrorAttribute), +} + +struct DefaultErrorAttribute { + ty: Type, +} + pub(crate) fn expand( Initializer { + attrs, this, path, brace_token, fields, rest, - mut error, + error, }: Initializer, default_error: Option<&'static str>, pinned: bool, ) -> TokenStream { let mut errors =3D TokenStream::new(); + let mut error =3D error.map(|(_, err)| err); + if let Some(default_error) =3D attrs.iter().fold(None, |acc, attr| { + #[expect(irrefutable_let_patterns)] + if let InitializerAttribute::DefaultError(DefaultErrorAttribute { = ty }) =3D attr { + Some(ty.clone()) + } else { + acc + } + }) { + error.get_or_insert(default_error); + } if let Some(default_error) =3D default_error { - error.get_or_insert((Default::default(), syn::parse_str(default_er= ror).unwrap())); + error.get_or_insert(syn::parse_str(default_error).unwrap()); } - let error =3D error.map(|(_, err)| err).unwrap_or_else(|| { + + let error =3D error.unwrap_or_else(|| { errors.extend(quote_spanned!(brace_token.span.close()=3D> ::core::compile_error!("expected `? ` after `}`"); )); @@ -350,6 +372,7 @@ fn make_field_check( =20 impl Parse for Initializer { fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs =3D input.call(Attribute::parse_outer)?; let this =3D input.peek(Token![&]).then(|| input.parse()).transpos= e()?; let path =3D input.parse()?; let content; @@ -381,7 +404,19 @@ fn parse(input: syn::parse::ParseStream) -> syn::Resul= t { .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) .transpose()?; + let attrs =3D attrs + .into_iter() + .map(|a| { + if a.path().is_ident("default_error") { + a.parse_args::() + .map(InitializerAttribute::DefaultError) + } else { + Err(syn::Error::new_spanned(a, "unknown initializer at= tribute")) + } + }) + .collect::, _>>()?; Ok(Self { + attrs, this, path, brace_token, @@ -392,6 +427,16 @@ fn parse(input: syn::parse::ParseStream) -> syn::Resul= t { } } =20 +impl Parse for DefaultErrorAttribute { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ty =3D input.parse()?; + if !input.peek(End) { + return Err(input.error("expected end of input")); + } + Ok(Self { ty }) + } +} + impl Parse for This { fn parse(input: syn::parse::ParseStream) -> syn::Result { Ok(Self { --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B13F1482CF5; Thu, 8 Jan 2026 13:53:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880407; cv=none; b=aWhjdve3he8g7eaxlsdjvHXQj+2HZK+xTJXuHK1PCFKMOTrWu2sUPFzF+Gx/rchEa7rP8mM85cw3Qif5IyycXSqa5O0LzF2z4gGL7lImHe3iCd1DcFlAJDK//JEWegOB+6e8Ifn3Vljdc+mCEx3OmBAUtMfRW9XTV9nR8FGrgqA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880407; c=relaxed/simple; bh=lc42Fme75A/GIA7rI+FlJKMf8vj8Rw5uU2GNuIL1LqE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=e4KtPZ/gEJGS0rM+D5plVUchDGFvM/HFPcbfA7tw4c8YEkVlN19S8GKroxASdAt9kPAA8veuhjOBwQ7Or5E/3mAUjjHpYmeqH2ytCAOJ6KOdM5e99I6ATTf82/xLRGxrNEwZW3fGCBWXiFTB5ZapO6b8hkF19ixsiNxtJx54oX8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=castQypT; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="castQypT" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E566DC116C6; Thu, 8 Jan 2026 13:53:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880407; bh=lc42Fme75A/GIA7rI+FlJKMf8vj8Rw5uU2GNuIL1LqE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=castQypToVBzrpo61++J5lIEotHIFazoCG+gKb6mhZE0IZgBky48yRDBZICHGE3yj bqpLYBEgulTye/ycEYRiubpXEhdP9DBBFEenvUtzP0Xt7pAj3SP7foz2CawnIVLZmA TRWzlAVhVlh5d6nVofWbpduKirgdp+Q2rWSCqc7D2cA05vTHFdLFyZOkaTek+1FV89 sU9qqgelfeGd66XL9WG21m1JjXMpv+WcPj+reOva5gDKD8iYiQIFMpaUyBOy6phcJN f0vrUz+DC2M1sRERnt7qkjvr5LupiMGdizPr9PlIumA7XliFjLUCxq20uSnbi3SYCI lzt0bw42ZzTdg== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 10/12] rust: init: use `#[default_error(err)]` for the initializer macros Date: Thu, 8 Jan 2026 14:50:48 +0100 Message-ID: <20260108135127.3153925-11-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Initializer macros should use this attribute instead of manually parsing the macro's input. This is because the syntax is now parsed using `syn`, which permits more complex constructs to be parsed. In addition, this ensures that the kernel's initializer marcos will have the exact same syntax as the ones from pin-init. Signed-off-by: Benno Lossin --- rust/kernel/init.rs | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs index 917f7ef001fd..7a0d4559d7b5 100644 --- a/rust/kernel/init.rs +++ b/rust/kernel/init.rs @@ -219,20 +219,12 @@ fn init(init: impl Init, flags: Flags) -> er= ror::Result /// [`Error`]: crate::error::Error #[macro_export] macro_rules! try_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) =3D> { - ::pin_init::init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? $crate::error::Error) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) =3D> { - ::pin_init::init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? $err) - }; + ($($args:tt)*) =3D> { + ::pin_init::init!( + #[default_error($crate::error::Error)] + $($args)* + ) + } } =20 /// Construct an in-place, fallible pinned initializer for `struct`s. @@ -279,18 +271,10 @@ macro_rules! try_init { /// [`Error`]: crate::error::Error #[macro_export] macro_rules! try_pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }) =3D> { - ::pin_init::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? $crate::error::Error) - }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { - $($fields:tt)* - }? $err:ty) =3D> { - ::pin_init::pin_init!($(&$this in)? $t $(::<$($generics),*>)? { - $($fields)* - }? $err) - }; + ($($args:tt)*) =3D> { + ::pin_init::pin_init!( + #[default_error($crate::error::Error)] + $($args)* + ) + } } --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 50AC149C208; Thu, 8 Jan 2026 13:53:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880413; cv=none; b=BEmNl5frOs9S1KPlqdJOQCzjHRV0S3cp5NeNQfHw9HOXNnYRVpULksIhE97OJWi8yCNF8U8XzlnJxvi8+u0tPHZbpo/iOVnamFYeof1MVj1y8ds7/Xr/l1MHMC/6rvrgdE7FrlVtHJnGxYsgF0UgckCXxbrFWC982HVzSlexp9M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880413; c=relaxed/simple; bh=IjOb0Bj4zYXMgsGYZv0TAuaJufPLvBMAMz0pWhAiqII=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DLj7uPuAArpMHWYPlYOjBFUViv07GnNM8cTzr5f4YDCGfjre4KAyO2m/NgNIhG7P/RZfr093D+8g5AZKG4t8+cuZfCO3+jVvujtrLnRzBKnou8FJ/S6re+HHIh3F2Wwepk+wT8YZYDOAWoqiU3w4X68r2mBGcwWV2wiOXZqxYF4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EJNyZ4Sz; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EJNyZ4Sz" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 800BBC116C6; Thu, 8 Jan 2026 13:53:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880412; bh=IjOb0Bj4zYXMgsGYZv0TAuaJufPLvBMAMz0pWhAiqII=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EJNyZ4SzZSWYeTU/xVi4uCKFaI5aPYfL13v9/qsKSLK/tic+eLW9rYqPNtugHsVoV 1ts6XgzxTiUzqj/T5gmzg3bDS/2Wzwcv2OjaY0C/3dqyvVBc+S6IvuxQIxpTFpbw4p 1e3LKDboPZpEyG58dkrWnTQaY97A2bTbLXNGY6BnxORssWV/NKXkw8v/6COY+kTlyv 5bSNirqbLox8hbjDf4Y9PzuGSalktZyA7YESvo69/jBsoXrqPPoi2TVMM2zy7m7vcw NwdK7WVY8wQaEgIibSniF+PjAzLlhWQLWDH0O+9k/jCFcUHF/2iRvrqp/RZl7/pIeB TV4BqUM25QVGA== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 11/12] rust: pin-init: internal: init: add support for attributes on initializer fields Date: Thu, 8 Jan 2026 14:50:49 +0100 Message-ID: <20260108135127.3153925-12-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Initializer fields ought to support the same attributes that are allowed in struct initializers on fields. For example, `cfg` or lint levels such as `expect`, `allow` etc. Add parsing support for these attributes using syn to initializer fields and adjust the macro expansion accordingly. Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/init.rs | 64 +++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/sr= c/init.rs index e14bacc88f41..c20be37e7fef 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -25,7 +25,12 @@ struct This { _in_token: Token![in], } =20 -enum InitializerField { +struct InitializerField { + attrs: Vec, + kind: InitializerKind, +} + +enum InitializerKind { Value { ident: Ident, value: Option<(Token![:], Expr)>, @@ -42,7 +47,7 @@ enum InitializerField { }, } =20 -impl InitializerField { +impl InitializerKind { fn ident(&self) -> Option<&Ident> { match self { Self::Value { ident, .. } | Self::Init { ident, .. } =3D> Some= (ident), @@ -224,10 +229,11 @@ fn init_fields( slot: &Ident, ) -> TokenStream { let mut guards =3D vec![]; + let mut guard_attrs =3D vec![]; let mut res =3D TokenStream::new(); - for field in fields { - let init =3D match field { - InitializerField::Value { ident, value } =3D> { + for InitializerField { attrs, kind } in fields { + let init =3D match kind { + InitializerKind::Value { ident, value } =3D> { let mut value_ident =3D ident.clone(); let value_prep =3D value.as_ref().map(|value| &value.1).ma= p(|value| { // Setting the span of `value_ident` to `value`'s span= improves error messages @@ -250,21 +256,24 @@ fn init_fields( } }; quote! { + #(#attrs)* { #value_prep // SAFETY: TODO unsafe { #write(::core::ptr::addr_of_mut!((*#slot)= .#ident), #value_ident) }; } + #(#attrs)* #[allow(unused_variables)] let #ident =3D #accessor; } } - InitializerField::Init { ident, value, .. } =3D> { + InitializerKind::Init { ident, value, .. } =3D> { // Again span for better diagnostics let init =3D format_ident!("init", span =3D value.span()); if pinned { let project_ident =3D format_ident!("__project_{ident}= "); quote! { + #(#attrs)* { let #init =3D #value; // SAFETY: @@ -274,12 +283,14 @@ fn init_fields( // for `#ident`. unsafe { #data.#ident(::core::ptr::addr_of_mut= !((*#slot).#ident), #init)? }; } + #(#attrs)* // SAFETY: TODO #[allow(unused_variables)] let #ident =3D unsafe { #data.#project_ident(&mut = (*#slot).#ident) }; } } else { quote! { + #(#attrs)* { let #init =3D #value; // SAFETY: `slot` is valid, because we are ins= ide of an initializer @@ -291,20 +302,25 @@ fn init_fields( )? }; } + #(#attrs)* // SAFETY: TODO #[allow(unused_variables)] let #ident =3D unsafe { &mut (*#slot).#ident }; } } } - InitializerField::Code { block: value, .. } =3D> quote!(#[allo= w(unused_braces)] #value), + InitializerKind::Code { block: value, .. } =3D> quote! { + #(#attrs)* + #[allow(unused_braces)] + #value + }, }; res.extend(init); - if let Some(ident) =3D field.ident() { + if let Some(ident) =3D kind.ident() { // `mixed_site` ensures that the guard is not accessible to th= e user-controlled code. let guard =3D format_ident!("__{ident}_guard", span =3D Span::= mixed_site()); - guards.push(guard.clone()); res.extend(quote! { + #(#attrs)* // Create the drop guard: // // We rely on macro hygiene to make it impossible for user= s to access this local @@ -316,13 +332,18 @@ fn init_fields( ) }; }); + guards.push(guard); + guard_attrs.push(attrs); } } quote! { #res // If execution reaches this point, all fields have been initializ= ed. Therefore we can now // dismiss the guards by forgetting them. - #(::core::mem::forget(#guards);)* + #( + #(#guard_attrs)* + ::core::mem::forget(#guards); + )* } } =20 @@ -332,7 +353,10 @@ fn make_field_check( init_kind: InitKind, path: &Path, ) -> TokenStream { - let fields =3D fields.iter().filter_map(|f| f.ident()); + let field_attrs =3D fields + .iter() + .filter_map(|f| f.kind.ident().map(|_| &f.attrs)); + let field_name =3D fields.iter().filter_map(|f| f.kind.ident()); match init_kind { InitKind::Normal =3D> quote! { // We use unreachable code to ensure that all fields have been= mentioned exactly once, @@ -343,7 +367,8 @@ fn make_field_check( let _ =3D || unsafe { ::core::ptr::write(slot, #path { #( - #fields: ::core::panic!(), + #(#field_attrs)* + #field_name: ::core::panic!(), )* }) }; @@ -361,7 +386,8 @@ fn make_field_check( zeroed =3D ::core::mem::zeroed(); ::core::ptr::write(slot, #path { #( - #fields: ::core::panic!(), + #(#field_attrs)* + #field_name: ::core::panic!(), )* ..zeroed }) @@ -382,7 +408,7 @@ fn parse(input: syn::parse::ParseStream) -> syn::Result= { let lh =3D content.lookahead1(); if lh.peek(End) || lh.peek(Token![..]) { break; - } else if lh.peek(Ident) || lh.peek(Token![_]) { + } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Toke= n![#]) { fields.push_value(content.parse()?); let lh =3D content.lookahead1(); if lh.peek(End) { @@ -448,6 +474,16 @@ fn parse(input: syn::parse::ParseStream) -> syn::Resul= t { } =20 impl Parse for InitializerField { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs =3D input.call(Attribute::parse_outer)?; + Ok(Self { + attrs, + kind: input.parse()?, + }) + } +} + +impl Parse for InitializerKind { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lh =3D input.lookahead1(); if lh.peek(Token![_]) { --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF57F482CF3; Thu, 8 Jan 2026 13:53:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880419; cv=none; b=Dp6AmO8TFOpEqH0rfWl3/Xkf7ARr+sdoG04HJeP714l3Z7bfrjR8oLeBscBtbl8AIvkDquMhIoB0y/M8qivf0H3mfpk5WrIBi77/Yg/53rPNY53iG9h2CGv9VtorTzUEXYxRP1IkEQXPpKtlfWp6JczkSJM3x7Owp1smjhDEdiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880419; c=relaxed/simple; bh=8i7hyy6H3ncmvFOSoGX4b1QEy/WGFT0fkcqvwKoV2w8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lOCVuudEs1gzdyo2NCNjWzd4qKelnvmiIiykDM6DV6L0m6ZXE/qhljky/DBNBK22f0dGJhqqoaeudeqSQsL2FkGBcDhqHHW/mzlddjjIoQG8sMJWaE00TtYGScTHZoRW9Lg+R5pwKHWrEZb2RSYa/NzzR4gYRieOlVzygoLENvs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=nsLoli7C; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="nsLoli7C" Received: by smtp.kernel.org (Postfix) with ESMTPSA id EDA9DC16AAE; Thu, 8 Jan 2026 13:53:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880418; bh=8i7hyy6H3ncmvFOSoGX4b1QEy/WGFT0fkcqvwKoV2w8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nsLoli7CbIEGDfmNm+Qs+oQ+ojk3oiFDwaigOaC+gTb+VGTJ/LHlf/xxyyxRcJecR N5aFvt479yiYcrPRM+mY2mSPjET/L4NEUvB+Bkd0fahI2LzM3UC/YN/rZHm7wcBSVC xZK/5tzrZCN3sskNFFS045TzBE53ru67O/ozC34uA+e3RQlrM8u6pLKX6E64Ugc+G6 mGWdgFMpSTntjD2EeM7x10K1/VVAnXF/v1kAYRqHdwiMPKlv2zGG+uyUSP++Xe7BYm ioUFXXYFNYoLnS8qFb1KrnmUyEXUQJ+HkaICeLhbsvUnyUMEYdZA7jEJE1LisCc49O H5bSgQV+BWyig== From: Benno Lossin To: Benno Lossin , Gary Guo , Miguel Ojeda , Boqun Feng , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: Janne Grunau , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 12/12] rust: pin-init: internal: init: add escape hatch for referencing initialized fields Date: Thu, 8 Jan 2026 14:50:50 +0100 Message-ID: <20260108135127.3153925-13-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The initializer macro emits mutable references for already initialized fields, which allows modifying or accessing them later in code blocks or when initializing other fields. This behavior results in compiler errors when combining with packed structs, since those do not permit creating references to misaligned fields. For example: #[repr(C, packed)] struct Foo { a: i8, b: i32, } fn main() { let _ =3D init!(Foo { a: -42, b: 42 }); } This will lead to an error like this: error[E0793]: reference to field of packed struct is unaligned --> tests/ui/compile-fail/init/packed_struct.rs:10:13 | 10 | let _ =3D init!(Foo { a: -42, b: 42 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | =3D note: this struct is 1-byte aligned, but the type of this field = may require higher alignment =3D note: creating a misaligned reference is undefined behavior (eve= n if that reference is never dereferenced) =3D help: copy the field contents to a local variable, or replace th= e reference with a raw pointer and use `read_unaligned`/`write_unaligned` (= loads and stores via `*p` must be properly aligned even when using raw poin= ters) =3D note: this error originates in the macro `init` (in Nightly buil= ds, run with -Z macro-backtrace for more info) This was requested by Janne Grunau [1] and will most certainly be used by the kernel when we eventually end up with trying to initialize packed structs. Thus add an initializer attribute `#[disable_initialized_field_access]` that does what the name suggests: do not generate references to already initialized fields. There is space for future work: add yet another attribute which can be applied on fields of initializers that ask for said field to be made accessible. We can add that when the need arises. Requested-by: Janne Grunau Link: https://lore.kernel.org/all/20251206170214.GE1097212@robin.jannau.net= [1] Signed-off-by: Benno Lossin --- rust/pin-init/internal/src/init.rs | 75 +++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/sr= c/init.rs index c20be37e7fef..148f43bf3c7b 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -58,6 +58,7 @@ fn ident(&self) -> Option<&Ident> { =20 enum InitializerAttribute { DefaultError(DefaultErrorAttribute), + DisableInitializedFieldAccess, } =20 struct DefaultErrorAttribute { @@ -80,7 +81,6 @@ pub(crate) fn expand( let mut errors =3D TokenStream::new(); let mut error =3D error.map(|(_, err)| err); if let Some(default_error) =3D attrs.iter().fold(None, |acc, attr| { - #[expect(irrefutable_let_patterns)] if let InitializerAttribute::DefaultError(DefaultErrorAttribute { = ty }) =3D attr { Some(ty.clone()) } else { @@ -142,7 +142,15 @@ fn assert_zeroable(_: *mut = T) }; // `mixed_site` ensures that the data is not accessible to the user-co= ntrolled code. let data =3D format_ident!("__data", span =3D Span::mixed_site()); - let init_fields =3D init_fields(&fields, pinned, &data, &slot); + let init_fields =3D init_fields( + &fields, + pinned, + !attrs + .iter() + .any(|attr| matches!(attr, InitializerAttribute::DisableInitia= lizedFieldAccess)), + &data, + &slot, + ); let field_check =3D make_field_check(&fields, init_kind, &path); quote! {{ // We do not want to allow arbitrary returns, so we declare this t= ype as the `Ok` return @@ -225,6 +233,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, erro= rs: &mut TokenStream) -> fn init_fields( fields: &Punctuated, pinned: bool, + generate_initialized_accessors: bool, data: &Ident, slot: &Ident, ) -> TokenStream { @@ -255,6 +264,13 @@ fn init_fields( unsafe { &mut (*#slot).#ident } } }; + let accessor =3D generate_initialized_accessors.then(|| { + quote! { + #(#attrs)* + #[allow(unused_variables)] + let #ident =3D #accessor; + } + }); quote! { #(#attrs)* { @@ -262,37 +278,31 @@ fn init_fields( // SAFETY: TODO unsafe { #write(::core::ptr::addr_of_mut!((*#slot)= .#ident), #value_ident) }; } - #(#attrs)* - #[allow(unused_variables)] - let #ident =3D #accessor; + #accessor } } InitializerKind::Init { ident, value, .. } =3D> { // Again span for better diagnostics let init =3D format_ident!("init", span =3D value.span()); - if pinned { + let (value_init, accessor) =3D if pinned { let project_ident =3D format_ident!("__project_{ident}= "); - quote! { - #(#attrs)* - { - let #init =3D #value; + ( + 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 correc= t trait (`Init` or `PinInit`) // for `#ident`. unsafe { #data.#ident(::core::ptr::addr_of_mut= !((*#slot).#ident), #init)? }; - } - #(#attrs)* - // SAFETY: TODO - #[allow(unused_variables)] - let #ident =3D unsafe { #data.#project_ident(&mut = (*#slot).#ident) }; - } + }, + quote! { + // SAFETY: TODO + unsafe { #data.#project_ident(&mut (*#slot).#i= dent) } + }, + ) } else { - quote! { - #(#attrs)* - { - let #init =3D #value; + ( + quote! { // SAFETY: `slot` is valid, because we are ins= ide of an initializer // closure, we return when an error/panic occu= rs. unsafe { @@ -301,12 +311,27 @@ fn init_fields( ::core::ptr::addr_of_mut!((*#slot).#id= ent), )? }; - } + }, + quote! { + // SAFETY: TODO + unsafe { &mut (*#slot).#ident } + }, + ) + }; + let accessor =3D generate_initialized_accessors.then(|| { + quote! { #(#attrs)* - // SAFETY: TODO #[allow(unused_variables)] - let #ident =3D unsafe { &mut (*#slot).#ident }; + let #ident =3D #accessor; + } + }); + quote! { + #(#attrs)* + { + let #init =3D #value; + #value_init } + #accessor } } InitializerKind::Code { block: value, .. } =3D> quote! { @@ -436,6 +461,10 @@ fn parse(input: syn::parse::ParseStream) -> syn::Resul= t { if a.path().is_ident("default_error") { a.parse_args::() .map(InitializerAttribute::DefaultError) + } else if a.path().is_ident("disable_initialized_field_acc= ess") { + a.meta + .require_path_only() + .map(|_| InitializerAttribute::DisableInitializedF= ieldAccess) } else { Err(syn::Error::new_spanned(a, "unknown initializer at= tribute")) } --=20 2.51.2 From nobody Sun Feb 8 00:49:37 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9E9D44A1583; Thu, 8 Jan 2026 13:53:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880421; cv=none; b=OTa+p1vcaIbudb2Y4pMp88hBEqWxxIuS6ZmmkQvawOhunxtwmb7DoRuaahqDsSCaf9x/8aiub/UqP8YzxW0RAreY2ZfIvVrpHBUX8xGrpUSQ/oK20RMxNx2jH/3SwZcHpZKNtbmBtAqAxNaedHf/BmjPRezaCewm1mufLhlZo1w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767880421; c=relaxed/simple; bh=n1TOtkvhO6+E53eScgAZDM8tntkQ8tIpB7iBVPGyURQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VzY8dlJYVX8fecH9eZc4UpX96o9LJmKltWkFBbmMei6aAIt3Hu1yzjOSGqpUMT6XyLDR+obYc6E0M07JkI0plFNwhWtAY6/SJUAWb2WVQPMSc910cBYmAhKDcIploSfS8oi/nAXtwIyet+PkKgZ4owZUykDJgRG3yvI0yAiCMIA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=hUJeYlF9; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="hUJeYlF9" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 25FF4C116C6; Thu, 8 Jan 2026 13:53:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1767880421; bh=n1TOtkvhO6+E53eScgAZDM8tntkQ8tIpB7iBVPGyURQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hUJeYlF9dgKiHGP/pd49DVBz09n9S0y+sBJdMss6DpGfgjCrxuMWTwp00Jy4yV+Bb saL9kDEQDdig7/ENjn4J8X8PqLoULccOPxucYHXVdXg8Q7H3d7dTkIXNsJR5Yj94P4 I0+SeEYpQEl7wCG7TZq3XGLVZCviiIq7Fl5GQIdSuaO5tebf+WuOSTlcuN4I02qFoq 4ePvij98I7k90y+TPOyN5SIQuIpXZjIypXi/tYqHRJ3c78e4CLsh9zpR5ewCUIFRBo dav0LYYpYmIZnQ1H/asycgf5d9ygyF3xxmiX9Mebi1i8UJ9/5/PkbyDYf08YciHLUn u+lFVybIFyKVA== From: Benno Lossin To: Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org Subject: [PATCH 13/13] MAINTAINERS: add Gary Guo to pin-init Date: Thu, 8 Jan 2026 14:50:51 +0100 Message-ID: <20260108135127.3153925-14-lossin@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260108135127.3153925-1-lossin@kernel.org> References: <20260108135127.3153925-1-lossin@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Gary has been involved in pin-init since the very beginning. He created his own version before I even joined Rust-for-Linux, contributed ideas to my solution, and reviewed it. With the removal of the declarative macro mess, he is also going to be a maintainer of pin-init. Acked-by: Gary Guo Signed-off-by: Benno Lossin --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index dc731d37c8fe..fb6534339b75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22916,6 +22916,7 @@ F: rust/kernel/num/ =20 RUST [PIN-INIT] M: Benno Lossin +M: Gary Guo L: rust-for-linux@vger.kernel.org S: Maintained W: https://rust-for-linux.com/pin-init --=20 2.51.2