rust: pin-init: internal: init: support tuple structs in [pin_]init!

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 ]
Link: https://patch.msgid.link/20260904-tuple-struct-v1-3-72c50bd037fd@garyguo.net
Signed-off-by: Gary Guo <gary@garyguo.net>
This commit is contained in:
Mohamad Alsadhan 2026-09-04 15:12:31 +01:00 committed by Gary Guo
parent 1ee699c451
commit d48aa955cb
2 changed files with 95 additions and 52 deletions

View File

@ -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())
}

View File

@ -592,7 +592,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.
@ -626,6 +626,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
@ -744,9 +766,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>`]
@ -785,7 +809,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.