Rust如何自动将Vec<u8>或[u8;N]格式化为十六进制字符串减少重复代码
方案1:封装通用工具函数(零额外依赖,改动最小)
先把字节转十六进制字符串的逻辑抽成公共函数,所有结构体的Display实现直接调用即可,大幅减少重复代码:
use std::fmt; // 通用字节转十六进制字符串函数 fn bytes_to_hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{:02X}", b)).collect() } #[derive(Debug)] struct Foo { bar: Vec<u8>, baz: [u8; 2], } impl fmt::Display for Foo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Foo {{ bar: 0x{}, baz: 0x{} }}", bytes_to_hex(&self.bar), bytes_to_hex(&self.baz) ) } } // 其他结构体只需要重复上面的Display实现逻辑,不需要再写转换代码 #[derive(Debug)] struct AnotherStruct { data: Vec<u8>, id: [u8; 16], } impl fmt::Display for AnotherStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AnotherStruct {{ data: 0x{}, id: 0x{} }}", bytes_to_hex(&self.data), bytes_to_hex(&self.id) ) } }
如果想要更高性能,可以直接往Formatter里写,避免额外的String分配:
fn write_hex(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { for b in bytes { write!(f, "{:02X}", b)?; } Ok(()) } // 调用的时候 impl fmt::Display for Foo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Foo {{ bar: 0x")?; write_hex(f, &self.bar)?; write!(f, ", baz: 0x")?; write_hex(f, &self.baz)?; write!(f, " }}") } }
方案2:使用newtype包裹字节类型(无需每个结构体实现Display)
如果你的字节字段语义统一,可以定义一个通用的十六进制字节newtype,直接给它实现Display,后续结构体里的字节字段直接用这个类型即可:
use std::fmt; #[derive(Debug, Clone, PartialEq, Eq)] struct HexBytes<T: AsRef<[u8]>>(pub T); impl<T: AsRef<[u8]>> fmt::Display for HexBytes<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "0x")?; for b in self.0.as_ref() { write!(f, "{:02X}", b)?; } Ok(()) } } // 结构体直接用这个类型,不需要单独实现Display #[derive(Debug)] struct Foo { bar: HexBytes<Vec<u8>>, baz: HexBytes<[u8; 2]>, } fn main() { let foo = Foo { bar: HexBytes(vec![0x1, 0x2, 0x3]), baz: HexBytes([0x4, 0x5]), }; // 直接打印字段就是十六进制格式 println!("Foo {{ bar: {}, baz: {} }}", foo.bar, foo.baz); // 输出:Foo { bar: 0x010203, baz: 0x0405 } }
方案3:使用派生宏实现自动格式化
如果想要完全避免手写Display实现,可以用社区的派生宏工具,比如derivative crate可以自定义输出格式,针对特定字段指定十六进制格式化,效果和需求完全一致:
use derivative::Derivative; use std::fmt; fn format_hex<T: AsRef<[u8]>>(bytes: &T, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "0x")?; for b in bytes.as_ref() { write!(f, "{:02X}", b)?; } Ok(()) } #[derive(Derivative, Debug)] #[derivative(Display)] struct Foo { #[derivative(Display(format_with = "format_hex"))] bar: Vec<u8>, #[derivative(Display(format_with = "format_hex"))] baz: [u8; 2], }
这种方式只需要给每个需要十六进制输出的字段加一行属性标注即可,完全不需要手写Display的实现逻辑。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

