From: Malte Wechter <maltewechter@gmail.com>
To: "Gary Guo" <gary@garyguo.net>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Breno Leitao" <leitao@debian.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Jens Axboe" <axboe@kernel.dk>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
linux-block@vger.kernel.org
Subject: Re: [PATCH v2] rust: add procedural macro for declaring configfs attributes
Date: Wed, 10 Jun 2026 11:15:05 +0200 [thread overview]
Message-ID: <f6a513e8-227f-4b22-bd4d-81d88536eff1@gmail.com> (raw)
In-Reply-To: <DIZHKJSZXQ4Z.6SPXJVGDV9ZW@garyguo.net>
On 6/3/26 4:32 PM, Gary Guo wrote:
> On Wed Jun 3, 2026 at 3:08 PM BST, Malte Wechter wrote:
>> Implement `configfs_attrs!` as a procedural macro using `syn`, this
>> improves readability and maintainability. Remove the old macro and
>> replace all uses with the new macro. Add the new macro implementation
>> file to MAINTAINERS.
>>
>> Signed-off-by: Malte Wechter <maltewechter@gmail.com>
>> ---
>> Changes in v2:
>> - Add a try_parse helper function to macros/helpers.rs
>> - Fix bug where 'child' configuration gets dropped if trailing comma is missing (sashiko)
>> - Link to v1: https://lore.kernel.org/r/20260520-configfs-syn-v1-1-6c5b80a9cef2@gmail.com
>> ---
>> MAINTAINERS | 1 +
>> drivers/block/rnull/configfs.rs | 2 +-
>> rust/kernel/configfs.rs | 251 ----------------------------------------
>> rust/macros/configfs_attrs.rs | 182 +++++++++++++++++++++++++++++
>> rust/macros/helpers.rs | 15 +++
>> rust/macros/lib.rs | 85 ++++++++++++++
>> samples/rust/rust_configfs.rs | 2 +-
>> 7 files changed, 285 insertions(+), 253 deletions(-)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2fb1c75afd163..45f7a1ec93b45 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -6464,6 +6464,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git config
>> F: fs/configfs/
>> F: include/linux/configfs.h
>> F: rust/kernel/configfs.rs
>> +F: rust/macros/configfs_attrs.rs
>> F: samples/configfs/
>> F: samples/rust/rust_configfs.rs
>>
>> diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
>> index 7c2eb5c0b7228..f28ec69d79846 100644
>> --- a/drivers/block/rnull/configfs.rs
>> +++ b/drivers/block/rnull/configfs.rs
>> @@ -4,8 +4,8 @@
>> use kernel::{
>> block::mq::gen_disk::{GenDisk, GenDiskBuilder},
>> configfs::{self, AttributeOperations},
>> - configfs_attrs,
>> fmt::{self, Write as _},
>> + macros::configfs_attrs,
>> new_mutex,
>> page::PAGE_SIZE,
>> prelude::*,
>> diff --git a/rust/macros/configfs_attrs.rs b/rust/macros/configfs_attrs.rs
>> new file mode 100644
>> index 0000000000000..a7fc75cdebcc0
>> --- /dev/null
>> +++ b/rust/macros/configfs_attrs.rs
>> @@ -0,0 +1,182 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +use quote::{
>> + quote, //
>> + ToTokens,
>> +};
>> +
>> +use proc_macro2::{
>> + Span, //
>> +};
>> +
>> +use syn::{
>> + bracketed,
>> + parse::{
>> + Parse,
>> + ParseStream, //
>> + },
>> + punctuated::Punctuated,
>> + spanned::Spanned,
>> + Ident,
>> + LitInt,
>> + Token,
>> + Type, //
>> +};
>> +
>> +use crate::helpers::try_parse;
>> +
>> +pub(crate) struct ConfigfsAttrs {
>> + container: Type,
>> + data: Type,
>> + child: Option<Type>,
>> + attributes: Vec<(Ident, LitInt)>,
>> +}
>> +
>> +fn parse_attribute_field(stream: ParseStream<'_>) -> syn::Result<(Ident, LitInt)> {
>> + let id = stream.parse::<syn::Ident>()?;
>> + let _colon = stream.parse::<Token![:]>()?;
>> + let v = stream.parse::<LitInt>()?;
>> + Ok((id, v))
>> +}
>> +
>> +fn parse_named_field(stream: ParseStream<'_>, name: &str) -> syn::Result<Type> {
>> + let name_id = stream.parse::<Ident>()?;
>> + if name_id != name {
>> + return Err(parse_field_error(name_id.span(), &name_id, name));
>> + }
>> + let _colon = stream.parse::<Token![:]>()?;
>> + let v = stream.parse::<Type>()?;
>> + stream.parse::<Token![,]>()?;
>> + Ok(v)
>> +}
>> +
>> +fn parse_attributes(stream: ParseStream<'_>) -> syn::Result<Vec<(Ident, LitInt)>> {
>> + let attribute_id = stream.parse::<Ident>()?;
>> + if attribute_id != "attributes" {
>> + return Err(syn::Error::new(
>> + attribute_id.span(),
>> + format!(
>> + "unexpected identifier: {}, expected: 'attributes'",
>> + attribute_id
>> + ),
>> + ));
>> + }
>> + stream.parse::<Token![:]>()?;
>> + let attr_stream;
>> + let _bracket = bracketed!(attr_stream in stream);
>> + let attributes = Punctuated::<(Ident, LitInt), Token![,]>::parse_terminated_with(
>> + &attr_stream,
>> + parse_attribute_field,
>> + )?;
>> + let attributes = attributes.into_iter().collect::<Vec<_>>();
>> +
>> + stream.parse::<Option<Token![,]>>()?;
>> + Ok(attributes)
>> +}
>> +
>> +fn parse_field_error(span: Span, name: &Ident, expected_name: &str) -> syn::Error {
>> + syn::Error::new(
>> + span,
>> + format!("Unexpected field name, got: {name} expected: {expected_name}"),
>> + )
>> +}
>> +
>> +impl Parse for ConfigfsAttrs {
>> + fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
>> + let container = try_parse(input, |s| parse_named_field(s, "container"))?;
>> + let data = try_parse(input, |s| parse_named_field(s, "data"))?;
>> + let child = try_parse(input, |s| parse_named_field(s, "child")).ok();
>> + let attributes = parse_attributes(input)?;
> I have a `parse_ordered_fields!()` macro in module.rs, please extract it to
> helpers and use it instead of implementing the parsing in an ad-hoc manner.
That is a good idea.
>> +
>> + Ok(ConfigfsAttrs {
>> + container,
>> + data,
>> + child,
>> + attributes,
>> + })
>> + }
>> +}
>> +
>> +fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
>> + let raw_id = quote! { #ty }.to_string();
>> +
>> + // Sanitizing syn::Type::Path, this is safe since it is
>> + // only used as the identifier.
>> + let normalized = raw_id
>> + .split("::")
>> + .map(|s| String::from(s.trim()))
>> + .reduce(|a, b| format!("{a}_{b}"))
>> + .expect("Cannot be empty")
>> + .to_uppercase()
>> + .replace(|c: char| !c.is_alphanumeric(), "_");
>> +
>> + Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
>> +}
>> +
>> +pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
>> + let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
>> +
>> + let data_tp_ident = make_static_ident(data_ty, "TPE");
>> + let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");
> Instead of creating identifers like this, just scope them properly so that
> a fixed identifier is used without colliding.
I believe it is nice to keep the identifiers unique, especially if you
have many fields
you can easily differentiate them.
>> +
>> + let n = cfs_attrs.attributes.len() + 1;
>> +
>> + let attr_list = quote! {
>> + static #data_attr_ident: kernel::configfs::AttributeList<#n, #data_ty> =
>> + // SAFETY: We are expanding `configfs_attrs`.
>> + unsafe { kernel::configfs::AttributeList::new() };
>> + };
>> +
>> + let mut attrs = Vec::new();
>> + for (attr_idx, (name, id)) in cfs_attrs.attributes.iter().enumerate() {
>> + let name_with_attr = make_static_ident(name, "ATTR");
>> +
>> + let id: u64 = match id.base10_parse::<u64>() {
>> + Ok(v) => v,
>> + Err(_) => {
>> + return syn::Error::new(id.span(), "Could not parse attribute ID as a u64")
>> + .to_compile_error();
>> + }
>> + };
> Why parsing? The ID looks like it's just substituted as is.
For correctness, if you pass a negative integer literal it will panic
without this parsing.
This is not likely, but it provides a nice error message if this were to happen.
I can maybe move it to the parsing logic instead.
>
> Best,
> Gary
>
>> +
>> + attrs.push(quote! {
>> + static #name_with_attr: kernel::configfs::Attribute<#id, #data_ty, #data_ty> =
>> + // SAFETY: We are expanding `configfs_attrs`.
>> + unsafe {
>> + kernel::configfs::Attribute::new(kernel::c_str!(::core::stringify!(#name)))
>> + };
>> +
>> + // SAFETY: By design of this macro, the name of the variable we
>> + // invoke the `add` method on below, is not visible outside of
>> + // the macro expansion. The macro does not operate concurrently
>> + // on this variable, and thus we have exclusive access to the
>> + // variable.
>> + unsafe { #data_attr_ident.add::<#attr_idx, #id, _>(&#name_with_attr) }
>> + });
>> + }
>> +
>> + let has_child_code = if let Some(child) = cfs_attrs.child {
>> + quote! { new_with_child_ctor::<#n, #child>}
>> + } else {
>> + quote! { new::<#n> }
>> + };
>> +
>> + let data_type = quote! {
>> + {
>> + static #data_tp_ident:
>> + kernel::configfs::ItemType<#container_ty, #data_ty> =
>> + kernel::configfs::ItemType::<#container_ty, #data_ty>::#has_child_code(
>> + &THIS_MODULE, &#data_attr_ident
>> + );
>> + &#data_tp_ident
>> + }
>> + };
>> +
>> + quote! {
>> + {
>> + #attr_list
>> + #(#attrs)*
>> + #data_type
>> + }
>> + }
>> +}
>> diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
>> index d18fbf4daa0a5..fdab8804e1ba9 100644
>> --- a/rust/macros/helpers.rs
>> +++ b/rust/macros/helpers.rs
>> @@ -4,6 +4,7 @@
>> use quote::ToTokens;
>> use syn::{
>> parse::{
>> + discouraged::Speculative,
>> Parse,
>> ParseStream, //
>> },
>> @@ -54,6 +55,20 @@ pub(crate) fn file() -> String {
>> }
>> }
>>
>> +pub(crate) fn try_parse<T>(
>> + input: ParseStream<'_>,
>> + parser: impl FnOnce(ParseStream<'_>) -> Result<T>,
>> +) -> Result<T> {
>> + let fork = input.fork();
>> + match parser(&fork) {
>> + Ok(value) => {
>> + input.advance_to(&fork);
>> + Ok(value)
>> + }
>> + Err(e) => Err(e),
>> + }
>> +}
>> +
>> /// Obtain all `#[cfg]` attributes.
>> pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
>> attr.iter().filter(|a| a.path().is_ident("cfg"))
>> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
>> index 2cfd59e0f9e7c..745ba83c828b9 100644
>> --- a/rust/macros/lib.rs
>> +++ b/rust/macros/lib.rs
>> @@ -15,6 +15,8 @@
>> #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
>>
>> mod concat_idents;
>> +#[cfg(CONFIG_CONFIGFS_FS)]
>> +mod configfs_attrs;
>> mod export;
>> mod fmt;
>> mod helpers;
>> @@ -489,3 +491,86 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
>> .unwrap_or_else(|e| e.into_compile_error())
>> .into()
>> }
>> +
>> +/// Define a list of configfs attributes statically.
>> +///
>> +/// # Examples
>> +///
>> +/// ```ignore
>> +/// let item_type = configfs_attrs! {
>> +/// container: configfs::Subsystem<Configuration>,
>> +/// data: Configuration,
>> +/// child: Child,
>> +/// attributes: [
>> +/// message: 0,
>> +/// bar: 1,
>> +/// ],
>> +/// };
>> +///```
>> +///
>> +/// Expands the following output:
>> +/// let item_type = {
>> +/// static CONFIGURATION_ATTR_LIST: kernel::configfs::AttributeList<
>> +/// 3usize,
>> +/// Configuration,
>> +/// > = unsafe { kernel::configfs::AttributeList::new() };
>> +/// static MESSAGE_ATTR: kernel::configfs::Attribute<
>> +/// 0u64,
>> +/// Configuration,
>> +/// Configuration,
>> +/// > = unsafe {
>> +/// kernel::configfs::Attribute::new({
>> +/// const S: &str = "message\u{0}";
>> +/// const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
>> +/// S.as_bytes(),
>> +/// ) {
>> +/// Ok(v) => v,
>> +/// Err(_) => {
>> +/// ::core::panicking::panic_fmt(
>> +/// format_args!("string contains interior NUL"),
>> +/// );
>> +/// }
>> +/// };
>> +/// C
>> +/// })
>> +/// };
>> +/// unsafe { CONFIGURATION_ATTR_LIST.add::<0usize, 0u64, _>(&MESSAGE_ATTR) }
>> +/// static BAR_ATTR: kernel::configfs::Attribute<
>> +/// 1u64,
>> +/// Configuration,
>> +/// Configuration,
>> +/// > = unsafe {
>> +/// kernel::configfs::Attribute::new({
>> +/// const S: &str = "bar\u{0}";
>> +/// const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
>> +/// S.as_bytes(),
>> +/// ) {
>> +/// Ok(v) => v,
>> +/// Err(_) => {
>> +/// ::core::panicking::panic_fmt(
>> +/// format_args!("string contains interior NUL"),
>> +/// );
>> +/// }
>> +/// };
>> +/// C
>> +/// })
>> +/// };
>> +/// unsafe { CONFIGURATION_ATTR_LIST.add::<1usize, 1u64, _>(&BAR_ATTR) }
>> +/// {
>> +/// static CONFIGURATION_TPE: kernel::configfs::ItemType<
>> +/// Subsystem<Configuration>,
>> +/// Configuration,
>> +/// > = kernel::configfs::ItemType::<
>> +/// Subsystem<Configuration>,
>> +/// Configuration,
>> +/// >::new_with_child_ctor::<3usize, Child>(&THIS_MODULE, &CONFIGURATION_ATTR_LIST);
>> +/// &CONFIGURATION_TPE
>> +/// }
>> +/// };
>> +///
>> +#[cfg(CONFIG_CONFIGFS_FS)]
>> +#[proc_macro]
>> +pub fn configfs_attrs(input: TokenStream) -> TokenStream {
>> + configfs_attrs::configfs_attrs(parse_macro_input!(input as configfs_attrs::ConfigfsAttrs))
>> + .into()
>> +}
>> diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
>> index a1bd9db6010da..876462f7789d1 100644
>> --- a/samples/rust/rust_configfs.rs
>> +++ b/samples/rust/rust_configfs.rs
>> @@ -4,7 +4,7 @@
>>
>> use kernel::alloc::flags;
>> use kernel::configfs;
>> -use kernel::configfs::configfs_attrs;
>> +use kernel::macros::configfs_attrs;
>> use kernel::new_mutex;
>> use kernel::page::PAGE_SIZE;
>> use kernel::prelude::*;
>>
>> ---
>> base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
>> change-id: 20260417-configfs-syn-191e07130027
>>
>> Best regards,
Best regards
>
next prev parent reply other threads:[~2026-06-10 9:15 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-03 14:08 [PATCH v2] rust: add procedural macro for declaring configfs attributes Malte Wechter
2026-06-03 14:32 ` Gary Guo
2026-06-10 9:15 ` Malte Wechter [this message]
2026-06-10 10:05 ` Gary Guo
2026-06-10 14:48 ` Malte Wechter
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=f6a513e8-227f-4b22-bd4d-81d88536eff1@gmail.com \
--to=maltewechter@gmail.com \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=axboe@kernel.dk \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=leitao@debian.org \
--cc=linux-block@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox