Consider the following code:
#[derive(Debug)]
struct Test {
int: u8,
bits: u8,
hex: u8
}
fn main() {
let t = Test {
int: 1,
bits: 2,
hex: 3
};
println!("{:#?}", t);
}
When run, it outputs the following:
Test {
int: 1,
bits: 2,
hex: 3
}
That's cool that we can dump a struct with very little effort, but some of my data structures contain bit masks or other data not easily read in base 10. For the bits field, it would be much easier to read it they were output like 0b00000010
.
Is there a simple way to get output like this, without implementing the Debug
trait directly on the struct (a Debug
trait on another type would be fine)?
Test {
int: 1,
bits: 0b00000010,
hex: 0x03
}
I'm fine with using different types if I have to, but I'm hoping to keep the debug trait on the struct itself as simple as #[derive(Debug)]
.
Is there a simple way to get output like this, without implementing the
Debug
trait directly on the struct (aDebug
trait on another type would be fine)
Yes. #[derive(Debug)]
implements Debug
by just calling Debug::debug
on each of the members in turn. Follow the instructions in How to implement a custom 'fmt::Debug' trait? for your newtype wrapper:
use std::fmt;
#[derive(Debug)]
struct Test {
int: u8,
bits: Bits,
hex: u8,
}
struct Bits(u8);
impl fmt::Debug for Bits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0b{:08b}", self.0)
}
}
In a different direction, it would not be strange to have a method on your primary struct that returns another struct "for display purposes", akin to std::path::Display
. This allows you to move complicated display logic to a separate type while allowing your original struct to not have newtypes that might get in your way:
use std::fmt;
#[derive(Debug)]
struct Test {
int: u8,
bits: u8,
hex: u8,
}
impl Test {
fn pretty_debug(&self) -> PrettyDebug<'_> {
PrettyDebug {
int: &self.int,
bits: Bits(&self.bits),
hex: &self.hex,
}
}
}
#[derive(Debug)]
struct PrettyDebug<'a> {
int: &'a u8,
bits: Bits<'a>,
hex: &'a u8,
}
struct Bits<'a>(&'a u8);
impl fmt::Debug for Bits<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0b{:08b}", self.0)
}
}
It's a little silly to have a reference to a u8
, but references are the most generic solution here — choose appropriate data types for your case.
You could also implement Debug
directly for your PrettyDebug
type:
use std::fmt;
#[derive(Debug)]
struct Test {
int: u8,
bits: u8,
hex: u8,
}
impl Test {
fn pretty_debug(&self) -> PrettyDebug<'_> {
PrettyDebug(self)
}
}
struct PrettyDebug<'a>(&'a Test);
impl fmt::Debug for PrettyDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Test")
.field("int", &self.0.int)
.field("bits", &format_args!("0b{:08b}", self.0.bits))
.field("hex", &format_args!("0x{:02x}", self.0.hex))
.finish()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With