Automatically format Vec<u8> or [u8; N] as hexadecimal strings

Viewed 276

I have multiple structs where I want to format elements containing byte containers to hexadecimals strings. At the moment I have this implemented by overriding their fmt::Display in the following way:

use std::fmt;

#[derive(Debug)]
struct Foo {
    bar: Vec<u8>,
    baz: [u8; 2],
}

impl fmt::Display for Foo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let bar = self.bar.iter().map(|b| format!("{:02X}", b)).collect::<String>();
        let baz = self.baz.iter().map(|b| format!("{:02X}", b)).collect::<String>();
    
        write!(f, "Foo {{ bar: 0x{}, baz: 0x{} }}", bar, baz)
    }
}

fn main() {
    let foo = Foo {bar: vec![0x1, 0x2, 0x3], baz: [0x4, 0x5]};
    
    println!("Debug output: {:?}", foo);
    
    println!("Desired output: {}", foo);
}

Output:

Debug output: Foo { bar: [1, 2, 3], baz: [4, 5] }
Desired output: Foo { bar: 0x010203, baz: 0x0405 }

(playground)

With multiple structs, this leads to a bit of duplicate code.

Is there a more convenient way to implement this? For instance a macro similar to Debug that handles Vec<u8> and [u8] as hex strings?

2 Answers

You can use the derivative crate to add custom formatters for fields that you want as hex:

use derivative::Derivative;
use std::fmt;

fn format_hex<T: AsRef<[u8]>>(data: T, f: &mut fmt::Formatter) -> fmt::Result {
    f.write_str("0x")?;
    f.write_str(
        &data
            .as_ref()
            .iter()
            .map(|b| format!("{:02X}", b))
            .collect::<String>(),
    )
}

#[derive(Derivative)]
#[derivative(Debug)]
struct Foo {
    #[derivative(Debug(format_with = "format_hex"))]
    bar: Vec<u8>,
    #[derivative(Debug(format_with = "format_hex"))]
    baz: [u8; 2],
}

Playground link

One could use a Printer structure:

use std::fmt;

#[derive(Debug)]
struct Foo {
    bar: Vec<u8>,
    baz: [u8; 2],
}

struct PrinterHex<'a, 'b, 'c> {
    bar: &'a [u8],
    baz: &'b [u8],
    name: &'c str,
}

impl<'a, 'b, 'c> PrinterHex<'a, 'b, 'c> {
    fn from_foo<'d>(foo: &'d Foo) -> PrinterHex<'d, 'd, 'static> {
        PrinterHex {
            bar: &foo.bar,
            baz: &foo.baz,
            name: "Foo",
        }
    }
}

impl<'a, 'b, 'c> fmt::Display for PrinterHex<'a, 'b, 'c> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} {{ bar: 0x", self.name)?;
        for b in self.bar.iter() {
            write!(f, "{:02X}", b)?;
        }
        write!(f, ", baz: 0x")?;
        for b in self.baz.iter() {
            write!(f, "{:02X}", b)?;
        }
        write!(f, " }}")
    }
}

fn main() {
    let foo = Foo {
        bar: vec![0x1, 0x2, 0x3],
        baz: [0x4, 0x5],
    };

    let result = PrinterHex::from_foo(&foo).to_string();

    assert_eq!(result, "Foo { bar: 0x010203, baz: 0x0405 }");
}
Related