如何为所有派生EnumIter的Rust枚举统一实现Display trait?
我有多个结构简单但变体繁多的枚举,用strum crate管理这些枚举很方便。现在想基于Debug的行为,为所有派生了EnumIter的枚举统一实现Display trait。逐个枚举手动实现虽然可行,但太繁琐。
目前单个枚举的实现方式如下:
use std::fmt::{Display, Formatter}; use strum::IntoEnumIterator; use strum_macros::EnumIter; pub trait AllEnum: Sized { fn all_enum() -> Vec<Self>; } impl<T> AllEnum for T where T: IntoEnumIterator { fn all_enum() -> Vec<Self> { Self::iter().collect() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] enum Example { First, Second, Third } impl Display for Example { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } pub fn test() { let stuff = Example::all_enum(); println!("Core display works! {}, {}, {}", stuff[0], stuff[1], stuff[2]); } // 输出: Core display works! First, Second, Third
但这段Display的实现逻辑并不针对特定枚举,却要为每个枚举单独写。我尝试用 blanket implementation 统一处理:
impl<T> Display for T where T: IntoEnumIterator { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } }
结果触发了Rust的孤儿规则错误:
type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`) implementing a foreign trait is only possible if at least one of the types for which it is implemented is local only traits defined in the current crate can be implemented for a type parameter
我知道针对外部trait的 blanket implementation 没有完美解法,想问问有没有其他方式能实现统一为这些枚举添加Display的需求?
方案1:用strum自带的Display派生宏(最简便)
strum的strum_macros已经内置了Display派生功能,默认生成的实现就是输出枚举变体的名称,和Debug的{:?}输出完全一致。只需要给枚举同时派生Display即可:
use std::fmt::Display; use strum::IntoEnumIterator; use strum_macros::{EnumIter, Display}; pub trait AllEnum: Sized { fn all_enum() -> Vec<Self>; } impl<T> AllEnum for T where T: IntoEnumIterator { fn all_enum() -> Vec<Self> { Self::iter().collect() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, Display)] enum Example { First, Second, Third } pub fn test() { let stuff = Example::all_enum(); println!("Core display works! {}, {}, {}", stuff[0], stuff[1], stuff[2]); } // 输出: Core display works! First, Second, Third
这样完全不需要手动写Display实现,所有派生了EnumIter和Display的枚举都会自动获得符合需求的Display行为。
方案2:自定义派生宏(支持定制化)
如果后续需要修改Display的输出逻辑,可以自己写一个过程宏,统一为枚举生成Display实现。
首先在Cargo.toml添加依赖:
[dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full", "parsing"] } [lib] proc-macro = true
然后编写宏代码(比如放在src/lib.rs):
use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(DebugDisplay)] pub fn derive_debug_display(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; let expanded = quote! { impl std::fmt::Display for #name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } }; TokenStream::from(expanded) }
主代码中导入并使用这个宏:
use std::fmt::Display; use strum::IntoEnumIterator; use strum_macros::EnumIter; // 导入自定义宏,替换成你的 crate 名 use your_crate::DebugDisplay; pub trait AllEnum: Sized { fn all_enum() -> Vec<Self>; } impl<T> AllEnum for T where T: IntoEnumIterator { fn all_enum() -> Vec<Self> { Self::iter().collect() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, DebugDisplay)] enum Example { First, Second, Third } pub fn test() { let stuff = Example::all_enum(); println!("Core display works! {}, {}, {}", stuff[0], stuff[1], stuff[2]); }
这个方案的优势是可以随时修改宏中的Display逻辑,统一应用到所有使用该宏的枚举上。
方案3:本地包装类型(无额外依赖,侵入性强)
如果不想用宏,可以定义一个本地包装结构体,为它实现Display,使用时把枚举实例包进去:
use std::fmt::{Display, Formatter}; use strum::IntoEnumIterator; use strum_macros::EnumIter; pub trait AllEnum: Sized { fn all_enum() -> Vec<Self>; } impl<T> AllEnum for T where T: IntoEnumIterator { fn all_enum() -> Vec<Self> { Self::iter().collect() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] enum Example { First, Second, Third } // 定义本地包装类型 struct DebugDisplay<T>(pub T); impl<T: std::fmt::Debug> Display for DebugDisplay<T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.0) } } pub fn test() { let stuff = Example::all_enum(); println!("Core display works! {}, {}, {}", DebugDisplay(stuff[0]), DebugDisplay(stuff[1]), DebugDisplay(stuff[2])); } // 输出: Core display works! First, Second, Third
这个方案不需要额外依赖,但使用时必须手动包装枚举实例,适合临时场景。
内容的提问来源于stack exchange,提问作者Edward Peters

