Enum variant name as &str without Debug,Display,proc macro or heap allocation

you don't have to invent custom syntax, you can use macro_rules to parse normal rust enum definitions and extract the variant names. it is relative easy to parse common use cases, but if you want to cover every corner cases, it can get tedious and frustrating.

below is an example if you are only interested in the variant name (ignore the fields if any), it can parse common cases, but I didn't bother to make it perfectly robust. feel free to modify it to suit your need.

/// the macro name is arbitrarily chosen
/// ```
/// x! {
/// 	/// an example enum definition
/// 	pub enum Foo {
/// 		VariantX,
/// 		VariantY(i32),
/// 		VariantZ {
/// 			value: i32
/// 		}
/// 	}
/// }
/// ```
macro_rules! x {
	// "internal" entry point with saved raw tokens
	// matches enum definition, start recursion with empty accumulator
	{
		(@raw $($raw:tt)*)
		$(#[$_attr:meta])*
		$_vis:vis
		enum $E:ident {
			$($variants:tt)*
		}
	} => {
		 x! {
			(@raw $($raw)*)
			(@enum $E)
			(@variants)
			(@recur $($variants)*)
		 }
	};
	// final expansion, terminates recursion
	// simply emit original tokens, plus a `variant_name()` method
	{
		(@raw $($raw:tt)*)
		(@enum $E:path)
		(@variants $($V:ident)*)
		(@recur $(,)?)
	} => {
		// first emit original raw tokens
		$($raw)*
		// then implement `variant_name()` method
		impl $E {
			pub fn variant_name(&self) -> &'static str {
				match self {
					$(
						Self:: $V {..} => stringify!($V)
					),*
				}
			}
		}
	};
	// recursive invoke the same macro - order matters!
	// variant with named fields, capture variant name, ignore fields
	{
		(@raw $($raw:tt)*)
		(@enum $E:path)
		(@variants $($V:ident)*)
		(@recur
			$(,)?
			$V0:ident { $($_:tt)* }
			$($recur:tt)*
		)
	} => {
		x! {
			(@raw $($raw)*)
			(@enum $E)
			(@variants $($V)* $V0)
			(@recur $($recur)*)
		}
	};
	// variant with tuple fields, capture variant name, ignore fields
	{
		(@raw $($raw:tt)*)
		(@enum $E:path)
		(@variants $($V:ident)*)
		(@recur
			$(,)?
			$V0:ident ( $($_:tt)* )
			$($recur:tt)*
		)
	} => {
		x! {
			(@raw $($raw)*)
			(@enum $E)
			(@variants $($V)* $V0)
			(@recur $($recur)*)
		}
	};
	// fieldless variant:
	// variant with tuple fields, capture variant name, ignore fields
	{
		(@raw $($raw:tt)*)
		(@enum $E:path)
		(@variants $($V:ident)*)
		(@recur
			$(,)?
			$V0:ident
			$($recur:tt)*
		)
	} => {
		x! {
			(@raw $($raw)*)
			(@enum $E)
			(@variants $($V:ident)* $V0)
			(@recur $($recur)*)
		}
	};
	// catches error case, otherwise it will fallthrough to infinite recusrion
	{
		(@raw $($raw:tt)*)
		$($tt:tt)+
	} => {
		compile_error!("failed to parse enum definition")
	};
	// "real" entry point: matches and captures everything, must come last
	// this is not robust, if called
	($($raw:tt)*) => {
		x! {
			(@raw $($raw)*)
			$($raw)*
		}
	};
}
1 Like