Is there a dbg! alternative in Rust which doesn't break tuples into so many lines?

Viewed 681

I'm doing good old debug driven programming and I keep having readability issues because all tuples and structs get split up into million rows. Is there a way to somehow get a more compact dbg output?

e.g. I get from dbg!

[asd/src/data_source.rs:330] &lines[0] = InfluxLine {
    line_name: "line",
    tags: [
        (
            "label",
            "\"All\"",
        ),
        (
            "source",
            "wer",
        ),
        (
            "query",
            "reach_per_mode_hourly",
        ),
        (
            "query_type",
            "hourly_averages_coeff",
        ),
        (
            "measure_name",
            "reach",
        ),
    ],

Ideally this would be

[asd/src/data_source.rs:330] &lines[0] = InfluxLine {
    line_name: "line",
    tags: [
        ( "label", "\"All\"", ),
        ( "source", "wer", ),
        ( "query", "reach_per_mode_hourly", ),
        ( "query_type", "hourly_averages_coeff", ),
        ( "measure_name", "reach", ),
    ],
3 Answers

You can implement Debug manually to have full control over the formatting used by dbg. You can create a struct that holds a value and always formats it without pretty printing:

struct NoPrettyPrint<T: Debug>(T);

impl<T> Debug for NoPrettyPrint<T> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        // {:#?} never used here even if dbg!() specifies it
        write!(f, "{:?}", self.0)
    }
}

Use Formatter::debug_struct to manually implement Debug for InfluxLine:

impl Debug for InfluxLine {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        let tags: Vec<_> = self.tags.iter().map(NoPrettyPrint).collect();
        fmt.debug_struct("InfluxLine")
            .field("line_name", &self.line_name)
            .field("tags", &tags),
            .finish()
    }
}

As you showed what you want is that the pretty print does not apply to the tuples, one way that occurs to me to do that is to create a structure that implements the debug trait but this structure will only contain the tuple.

struct TupleDebug((String, String));
impl fmt::Debug for TupleDebug {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{:?}", self.0)
    }
}

...

let tuple_list = vec![
    TupleDebug(("asd".to_string(), "fefefe".to_string())),
    TupleDebug(("asd".to_string(), "fefefe".to_string())),
    TupleDebug(("asd".to_string(), "fefefe".to_string())),
    TupleDebug(("asd".to_string(), "fefefe".to_string())),
];
dbg!(tuple_list);

Result:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.76s
     Running `target/debug/playground`
[src/main.rs:17] tuple_list = [
    ("asd", "fefefe"),
    ("asd", "fefefe"),
    ("asd", "fefefe"),
    ("asd", "fefefe"),
]

Or Just use format!() and pass the formatted text to dbg!(), but all the text will be output on a single line:

let tuple_list = vec![
    ("asd".to_string(), "fefefe".to_string()),
    ("asd".to_string(), "fefefe".to_string()),
    ("asd".to_string(), "fefefe".to_string()),
    ("asd".to_string(), "fefefe".to_string()),
];
let debug_tuple_list = format!("{:?}", tuple_list);
dbg!(debug_tuple_list);

Result:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.76s
     Running `target/debug/playground`
[src/main.rs:17] format!("{:?}", tuple_list) = "[(\"asd\", \"fefefe\"), (\"asd\", \"fefefe\"), (\"asd\", \"fefefe\"), (\"asd\", \"fefefe\")]"

The first solution that came to my mind is to run rustfmt on the entire formatting output to fix the formatting.

Cons

  • with many calls to our dbg!, it may be slow and nearly impossible to get debug output
  • adds a dependency on an installed rustfmt
  • may break when debug-formatted stuff does not follow Rust grammar

Pros

  • with little code, we have your ideal output
  • may work best for complex debug outputs

Make sure you have rustfmt! cargo install rustfmt

Thanks to max-sixty for much of the code: https://github.com/rust-lang/rustfmt/issues/3257

The code + the example

use std::io::Write;
use std::process::{Command, Stdio};

fn format_rust_expression(value: &str) -> Option<String> {
    const PREFIX: &str = "const x:() = ";
    const SUFFIX: &str = ";\n";
    if let Ok(mut proc) = Command::new("rustfmt")
        .arg("--emit=stdout")
        .arg("--edition=2018")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
    {
        {
            let stdin = proc.stdin.as_mut().unwrap();
            stdin.write_all(PREFIX.as_bytes()).unwrap();
            stdin.write_all(value.as_bytes()).unwrap();
            stdin.write_all(SUFFIX.as_bytes()).unwrap();
        }
        if let Ok(output) = proc.wait_with_output() {
            if output.status.success() {
                // slice between after the prefix and before the suffix
                // (currently 14 from the start and 2 before the end, respectively)
                let start = PREFIX.len() + 1;
                let end = output.stdout.len() - SUFFIX.len();
                return Some(
                    ::std::str::from_utf8(&output.stdout[start..end])
                        .unwrap()
                        .to_owned(),
                );
            }
        }
    }
    None
}

fn my_dbg<T: ::std::fmt::Debug>(value: T) -> String {
    let formatted = format!("{:#?}", value);
    format_rust_expression(&formatted[..]).unwrap()
}

macro_rules! my_dbg {
    () => {
        std::eprintln!("[{}:{}]", std::file!(), std::line!());
    };
    ($val:expr) => {
        // Use of `match` here is intentional because it affects the lifetimes
        // of temporaries - https://stackoverflow.com/a/48732525/1063961
        match $val {
            tmp => {
                std::eprintln!("[{}:{}] {} = {}",
                    std::file!(), std::line!(), std::stringify!($val), my_dbg(&tmp));
                tmp
            }
        }
    };
    // Trailing comma with single argument is ignored
    ($val:expr,) => { $crate::my_dbg!($val) };
    ($($val:expr),+ $(,)?) => {
        ($($crate::my_dbg!($val)),+,)
    };
}

#[derive(Debug)]
struct InfluxLine {
    line_name: String,
    tags: Vec<(String, String)>,
}

fn main() {
    let value = InfluxLine {
        line_name: "line".to_string(),
        tags: vec![
            ("label".to_string(), "\"All\"".to_string()),
            ("source".to_string(), "wer".to_string()),
            ("query".to_string(), "reach_per_mode_hourly".to_string()),
            (
                "query_type".to_string(),
                "hourly_averages_coeff".to_string(),
            ),
            ("measure_name".to_string(), "reach".to_string()),
        ],
    };
    my_dbg!(value);
}
Related