From: Gary Guo <gary@garyguo.net>
To: "Benno Lossin" <lossin@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Gary Guo <gary@garyguo.net>, Mohamad Alsadhan <mo@sdhn.cc>
Subject: [PATCH 3/4] rust: pin-init: internal: init: support tuple structs in `[pin_]init!`
Date: Fri, 04 Sep 2026 15:12:31 +0100 [thread overview]
Message-ID: <20260904-tuple-struct-v1-3-72c50bd037fd@garyguo.net> (raw)
In-Reply-To: <20260904-tuple-struct-v1-0-72c50bd037fd@garyguo.net>
From: Mohamad Alsadhan <mo@sdhn.cc>
Extend the initializer syntax so that a field can be named by an index,
addressing tuple struct fields the same way a struct expression does:
pin_init!(Foo { 0: value, 1 <- initializer })
Tuple fields are not exposed by a `let` binding to the fields after them,
since they have no name to bind; `_0` would shadow a user variable.
Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
[ Fixed incorrect index calculation and cleaned up the code - Gary ]
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/internal/src/init.rs | 115 +++++++++++++++++++++----------------
rust/pin-init/src/lib.rs | 32 +++++++++--
2 files changed, 95 insertions(+), 52 deletions(-)
diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index fd0b5ea4a0a3..5920bb281a50 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -8,10 +8,13 @@
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
- token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type,
+ token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, LitInt, Member, Path, Token, Type,
};
-use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
+use crate::{
+ diagnostics::{DiagCtxt, ErrorGuaranteed},
+ util::*,
+};
pub(crate) struct Initializer {
attrs: Vec<InitializerAttribute>,
@@ -36,11 +39,11 @@ struct InitializerField {
enum InitializerKind {
Value {
- ident: Ident,
+ member: Member,
value: Option<(Token![:], Expr)>,
},
Init {
- ident: Ident,
+ member: Member,
_left_arrow_token: Token![<-],
value: Expr,
},
@@ -52,9 +55,9 @@ enum InitializerKind {
}
impl InitializerKind {
- fn ident(&self) -> Option<&Ident> {
+ fn member(&self) -> Option<&Member> {
match self {
- Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident),
+ Self::Value { member, .. } | Self::Init { member, .. } => Some(member),
Self::Code { .. } => None,
}
}
@@ -229,9 +232,9 @@ fn init_fields(
cfgs
};
- let ident = match kind {
- InitializerKind::Value { ident, .. } => ident,
- InitializerKind::Init { ident, .. } => ident,
+ let member = match kind {
+ InitializerKind::Value { member, .. } => member,
+ InitializerKind::Init { member, .. } => member,
InitializerKind::Code { block, .. } => {
let stmt = &block.stmts;
res.extend(quote! {
@@ -243,27 +246,28 @@ fn init_fields(
continue;
}
};
+ let ident = member.as_ident();
let slot = if pinned {
quote! {
// SAFETY:
// - `slot` is valid and properly aligned.
- // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
- // - `make_field_check` prevents `#ident` from being used twice, therefore
- // `(*slot).#ident` is exclusively accessed and has not been initialized.
+ // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned.
+ // - `make_field_check` prevents `#member` from being used twice, therefore
+ // `(*slot).#member` is exclusively accessed and has not been initialized.
(unsafe { #data.#ident(#slot) })
}
} else {
quote! {
// For `init!()` macro, everything is unpinned.
// SAFETY:
- // - `&raw mut (*slot).#ident` is valid.
- // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
- // - `make_field_check` prevents `#ident` from being used twice, therefore
- // `(*slot).#ident` is exclusively accessed and has not been initialized.
+ // - `&raw mut (*slot).#member` is valid.
+ // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned.
+ // - `make_field_check` prevents `#member` from being used twice, therefore
+ // `(*slot).#member` is exclusively accessed and has not been initialized.
(unsafe {
::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new(
- &raw mut (*#slot).#ident
+ &raw mut (*#slot).#member
)
})
}
@@ -273,11 +277,11 @@ fn init_fields(
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
let init = match kind {
- InitializerKind::Value { ident, value } => {
+ InitializerKind::Value { value, .. } => {
let value = value
.as_ref()
.map(|(_, value)| quote!(#value))
- .unwrap_or_else(|| quote!(#ident));
+ .unwrap_or_else(|| quote!(#member));
quote! {
#(#attrs)*
@@ -294,14 +298,23 @@ fn init_fields(
InitializerKind::Code { .. } => unreachable!(),
};
+ // A tuple field has no name that could be bound here (the `_0` identifiers are considered
+ // implementation detail and not user-facing).
+ let binding = match member {
+ Member::Named(ident) => quote! {
+ #(#cfgs)*
+ // Allow `non_snake_case` since the same warning is going to be reported for the
+ // struct field.
+ #[allow(unused_variables, non_snake_case)]
+ let #ident = #guard.let_binding();
+ },
+ Member::Unnamed(_) => quote!(),
+ };
+
res.extend(quote! {
#init
- #(#cfgs)*
- // Allow `non_snake_case` since the same warning is going to be reported for the struct
- // field.
- #[allow(unused_variables, non_snake_case)]
- let #ident = #guard.let_binding();
+ #binding
});
guards.push(guard);
@@ -326,9 +339,9 @@ fn make_field_check(
) -> TokenStream {
let field_attrs: Vec<_> = fields
.iter()
- .filter_map(|f| f.kind.ident().map(|_| &f.attrs))
+ .filter_map(|f| f.kind.member().map(|_| &f.attrs))
.collect();
- let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect();
+ let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect();
let zeroing_trailer = match init_kind {
InitKind::Normal => None,
InitKind::Zeroing => Some(quote! {
@@ -376,7 +389,8 @@ fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let lh = content.lookahead1();
if lh.peek(End) || lh.peek(Token![..]) {
break;
- } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) {
+ } else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#])
+ {
fields.push_value(content.parse()?);
let lh = content.lookahead1();
if lh.peek(End) {
@@ -450,31 +464,36 @@ fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
impl Parse for InitializerKind {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let lh = input.lookahead1();
- if lh.peek(Token![_]) {
- Ok(Self::Code {
+ let member = if lh.peek(Token![_]) {
+ return Ok(Self::Code {
_underscore_token: input.parse()?,
_colon_token: input.parse()?,
block: input.parse()?,
+ });
+ } else if lh.peek(Ident) || lh.peek(LitInt) {
+ input.parse::<Member>()?
+ } else {
+ return Err(lh.error());
+ };
+
+ let lh = input.lookahead1();
+ if lh.peek(Token![<-]) {
+ Ok(Self::Init {
+ member,
+ _left_arrow_token: input.parse()?,
+ value: input.parse()?,
+ })
+ } else if lh.peek(Token![:]) {
+ Ok(Self::Value {
+ member,
+ value: Some((input.parse()?, input.parse()?)),
+ })
+ } else if matches!(member, Member::Named(_)) && (lh.peek(Token![,]) || lh.peek(End)) {
+ // Short-hand syntax, available for named fields only.
+ Ok(Self::Value {
+ member,
+ value: None,
})
- } else if lh.peek(Ident) {
- let ident = input.parse()?;
- let lh = 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/src/lib.rs b/rust/pin-init/src/lib.rs
index bf77b76c43c8..f020b383851e 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -598,7 +598,7 @@ macro_rules! stack_try_pin_init {
};
}
-/// Construct an in-place, fallible pinned initializer for `struct`s.
+/// Construct an in-place, fallible pinned initializer for structs, including tuple structs.
///
/// The error type defaults to [`Infallible`]; if you need a different one, write `? Error` at the
/// end, after the struct initializer.
@@ -632,6 +632,28 @@ macro_rules! stack_try_pin_init {
/// # Box::pin_init(demo()).unwrap();
/// ```
///
+/// The fields of a tuple struct are addressed by their index:
+///
+/// ```rust
+/// # use pin_init::*;
+/// # use core::pin::Pin;
+/// #[pin_data]
+/// struct Pair(usize, Bar);
+///
+/// #[pin_data]
+/// struct Bar {
+/// x: u32,
+/// }
+///
+/// # fn demo() -> impl PinInit<Pair> {
+/// let initializer = pin_init!(Pair {
+/// 0: 42,
+/// 1 <- Bar { x: 64 },
+/// });
+/// # initializer }
+/// # Box::pin_init(demo()).unwrap();
+/// ```
+///
/// Arbitrary Rust expressions can be used to set the value of a variable.
///
/// The fields are initialized in the order that they appear in the initializer. So it is possible
@@ -750,9 +772,11 @@ macro_rules! stack_try_pin_init {
///
/// # Syntax
///
-/// As already mentioned in the examples above, inside of `pin_init!` a `struct` initializer with
-/// the following modifications is expected:
+/// As already mentioned in the examples above, inside of `pin_init!` a struct initializer with the
+/// following modifications is expected:
/// - Fields that you want to initialize in-place have to use `<-` instead of `:`.
+/// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They
+/// are not exposed by a `let` binding, since they have no name to bind.
/// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in
/// order to run arbitrary code.
/// - In front of the initializer you can write `&this in` to have access to a [`NonNull<Self>`]
@@ -791,7 +815,7 @@ macro_rules! stack_try_pin_init {
/// [`NonNull<Self>`]: core::ptr::NonNull
pub use pin_init_internal::pin_init;
-/// Construct an in-place, fallible initializer for `struct`s.
+/// Construct an in-place, fallible initializer for structs, including tuple structs.
///
/// This macro defaults the error to [`Infallible`]; if you need a different one, write `? Error`
/// at the end, after the struct initializer.
--
2.54.0
next prev parent reply other threads:[~2026-09-04 14:12 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-04 14:12 [PATCH 0/4] rust: pin-init: support tuple structs Gary Guo
2026-09-04 14:12 ` [PATCH 1/4] rust: pin-init: internal: extract utility code to new module Gary Guo
2026-09-04 14:12 ` [PATCH 2/4] rust: pin-init: internal: pin_data: support tuple struct projections Gary Guo
2026-09-04 14:12 ` Gary Guo [this message]
2026-09-04 14:12 ` [PATCH 4/4] rust: pin-init: internal: init: support tuple struct constructor syntax Gary Guo
2026-09-14 11:33 ` [PATCH 0/4] rust: pin-init: support tuple structs Gary Guo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260904-tuple-struct-v1-3-72c50bd037fd@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=mo@sdhn.cc \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.