[PATCH v6 03/10] rust: macros: add `convert` helper attribute

From: Kaiqi Guo

Date: Tue Sep 29 2026 - 10:02:26 EST


From: Jesung Yang <y.j3ms.n@xxxxxxxxx>

Introduce `#[convert(...)]`, a common helper attribute for the `Into`
and `TryFrom` derive macros. This helps avoid repeating the same types
in `#[into(...)]` and `#[try_from(...)]` when deriving both traits for
the same enum.

For example:

#[derive(Into, TryFrom)]
#[try_from(u8)]
#[into(u8)]
enum Enum {
A,
B,
}

The above snippet can be shortened to:

#[derive(Into, TryFrom)]
#[convert(u8)]
enum Enum {
A,
B,
}

Signed-off-by: Jesung Yang <y.j3ms.n@xxxxxxxxx>
Signed-off-by: Kaiqi Guo <chaoji_xinren@xxxxxxx>
---
rust/macros/convert.rs | 7 +++-
rust/macros/lib.rs | 78 +++++++++++++++++++++++++++++++++++++++---
2 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index c64e35447859..21d79bca16a0 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -150,6 +150,10 @@ fn get_helper_name(&self) -> &'static str {
Self::TryFrom => "try_from",
}
}
+
+ fn get_common_helper_name(&self) -> &'static str {
+ "convert"
+ }
}

fn parse_attrs(
@@ -157,6 +161,7 @@ fn parse_attrs(
attrs: &[Attribute],
) -> syn::Result<(Vec<Type>, bool, Option<Ident>)> {
let helper = target.get_helper_name();
+ let common_helper = target.get_common_helper_name();

let mut is_repr_c = false;
let mut repr_ty = None;
@@ -175,7 +180,7 @@ fn parse_attrs(
// Delegate `repr` attribute validation to rustc.
Ok(())
})?;
- } else if attr.path().is_ident(helper) {
+ } else if attr.path().is_ident(helper) || attr.path().is_ident(common_helper) {
let args = attr.parse_args_with(Punctuated::<Type, Token![,]>::parse_terminated)?;
helper_tys.extend(args);
}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 8b4f3e9dc860..cc6a96516c93 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -557,6 +557,10 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
/// - The macro uses the `into` custom attribute or `repr` attribute to generate [`From`]
/// implementations. `into` always takes precedence over `repr`.
///
+/// - In addition to `into`, the `convert` custom attribute is supported. The purpose of
+/// this attribute is to avoid repetition when deriving both [`Into`] and [`TryFrom`]
+/// for the same enum. See ["With `#[convert(...)]`"](#with-convert) for an example.
+///
/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
/// representation of discriminants is defined by rustc internally, and documentation
/// around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
@@ -568,7 +572,7 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
///
-/// # Supported types in `#[into(...)]`
+/// # Supported types in `#[into(...)]` and `#[convert(...)]`
///
/// - [`bool`]
/// - Primitive integer types (e.g., [`i8`], [`u8`])
@@ -650,6 +654,36 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
/// assert_eq!(Bounded::<u8, 4>::new::<1>(), foo_b);
/// ```
///
+/// ## With `#[convert(...)]`
+///
+/// The macro implements `From<Foo>` for each `T` specified in `#[into(...)]`,
+/// which always overrides `#[repr(...)]`. `#[convert(...)]` is useful when the same type
+/// needs to be specified in both `#[into(...)]` and `#[try_from(...)]`:
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::{
+/// macros::Into,
+/// macros::TryFrom,
+/// num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, Into, PartialEq, TryFrom)]
+/// #[convert(bool)]
+/// #[repr(u8)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B,
+/// }
+///
+/// assert_eq!(false, Foo::A.into());
+/// assert_eq!(true, Foo::B.into());
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+/// ```
+///
/// ## Compile-time Overflow Assertion
///
/// The following examples do not compile:
@@ -702,7 +736,7 @@ pub fn CovariantForLt(input: TokenStream) -> TokenStream {
/// #[repr(C)]
/// struct Foo(u8);
/// ```
-#[proc_macro_derive(Into, attributes(into))]
+#[proc_macro_derive(Into, attributes(into, convert))]
pub fn derive_into(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
convert::derive_into(input)
@@ -727,6 +761,11 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
/// - The macro uses the `try_from` custom attribute or `repr` attribute to generate
/// [`TryFrom`] implementations. `try_from` always takes precedence over `repr`.
///
+/// - In addition to `try_from`, the `convert` custom attribute is supported. The purpose
+/// of this attribute is to avoid repetition when deriving both [`Into`] and
+/// [`TryFrom`] for the same enum. See ["With `#[convert(...)]`"](#with-convert) for an
+/// example.
+///
/// - Currently, the macro does not support `repr(C)` fieldless enums since the actual
/// representation of discriminants is defined by rustc internally, and documentation
/// around it is not yet settled. See [Rust issue #124403] and [Rust PR #147017]
@@ -739,7 +778,7 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
/// [Rust issue #124403]: https://github.com/rust-lang/rust/issues/124403
/// [Rust PR #147017]: https://github.com/rust-lang/rust/pull/147017
///
-/// # Supported types in `#[try_from(...)]`
+/// # Supported types in `#[try_from(...)]` and `#[convert(...)]`
///
/// - [`bool`]
/// - Primitive integer types (e.g., [`i8`], [`u8`])
@@ -827,6 +866,37 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
/// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 4>::new::<1>()));
/// ```
///
+/// ## With `#[convert(...)]`
+///
+/// The macro implements `TryFrom<T>` for each `T` specified in `#[convert(...)]`,
+/// which always overrides `#[repr(...)]`. `#[convert(...)]` is useful when the same type
+/// needs to be specified in both `#[into(...)]` and `#[try_from(...)]`:
+///
+///
+/// ```rust
+/// # use kernel::prelude::*;
+/// use kernel::{
+/// macros::Into,
+/// macros::TryFrom,
+/// num::Bounded, //
+/// };
+///
+/// #[derive(Debug, Default, Into, PartialEq, TryFrom)]
+/// #[convert(bool)]
+/// #[repr(u8)]
+/// enum Foo {
+/// #[default]
+/// A,
+/// B,
+/// }
+///
+/// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+/// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+///
+/// assert_eq!(false, Foo::A.into());
+/// assert_eq!(true, Foo::B.into());
+/// ```
+///
/// ## Compile-time Overflow Assertion
///
/// The following examples do not compile:
@@ -879,7 +949,7 @@ pub fn derive_into(input: TokenStream) -> TokenStream {
/// #[repr(C)]
/// struct Foo(u8)
/// ```
-#[proc_macro_derive(TryFrom, attributes(try_from))]
+#[proc_macro_derive(TryFrom, attributes(try_from, convert))]
pub fn derive_try_from(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
convert::derive_try_from(input)