Repository: rust-bitcoin/rust-bitcoin (language: rust)

Issue:
Make `Debug` representation of `Witness` to be slice of hex-encoded bytes strings
Currently `Debug` shows all the `Witness` fields including internals like `indices_start`. I suppose it also shows serialized indices (I've only seen an empty one). This is hard to read even for experienced developers.

We should change it to just show it as a slice of hex-encoded byte strings.

(In general, I think `Transaction` should look similar to Bitcoin Core's output.)


Discussion / hints:
I'd kinda like it to display some pseduo-fields like the number of elements, total length of all elements, etc.

Independently it would be nice if the actual witness data were displayed as a list of hex-encoded strings, rather than one concatenated blob or as a list of u8s.
> I'd kinda like it to display some pseduo-fields like the number of elements, total length of all elements

I was thinking about that too, but I think it'd be too weird? It'd be the only type in the entire Rust ecosystem to do that.

> Independently it would be nice if the actual witness data were displayed as a list of hex-encoded strings

OMG! That's what I meant but I wrote it in a way that absolutely doesn't look like it.
> It'd be the only type in the entire Rust ecosystem to do that.

I don't think so. I'm sure we've done it elsewhere in this crate family even. And it's done in several places in std. Just grep for `debug_struct` and see what kind of "field"s people are making.
I've seen it for skipping/censoring fields, not adding fake ones.

Fix patch:
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index d48755992..e396da8fc 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -9,6 +9,7 @@ use core::ops::Index;
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
+use hex::DisplayHex;
 use internals::compact_size;
 
 use crate::prelude::Vec;
@@ -231,76 +232,29 @@ fn decode_cursor(bytes: &[u8], start_of_indices: usize, index: usize) -> Option<
     }
 }
 
-impl fmt::Debug for Witness {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        if f.alternate() {
-            fmt_debug_pretty(self, f)
-        } else {
-            fmt_debug(self, f)
-        }
-    }
-}
+/// Debug implementation that displays the witness as a structured output containing hex-encoded witness elements.
+struct DebugElements<'a>(&'a Witness);
 
-fn fmt_debug(w: &Witness, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-    #[rustfmt::skip]
-    let comma_or_close = |current_index, last_index| {
-        if current_index == last_index { "]" } else { ", " }
-    };
-
-    f.write_str("Witness: { ")?;
-    write!(f, "indices: {}, ", w.witness_elements)?;
-    write!(f, "indices_start: {}, ", w.indices_start)?;
-    f.write_str("witnesses: [")?;
-
-    let instructions = w.iter();
-    match instructions.len().checked_sub(1) {
-        Some(last_instruction) => {
-            for (i, instruction) in instructions.enumerate() {
-                let bytes = instruction.iter();
-                match bytes.len().checked_sub(1) {
-                    Some(last_byte) => {
-                        f.write_str("[")?;
-                        for (j, byte) in bytes.enumerate() {
-                            write!(f, "{:#04x}", byte)?;
-                            f.write_str(comma_or_close(j, last_byte))?;
-                        }
-                    }
-                    None => {
-                        // This is possible because the varint is not part of the instruction (see Iter).
-                        write!(f, "[]")?;
-                    }
-                }
-                f.write_str(comma_or_close(i, last_instruction))?;
-            }
-        }
-        None => {
-            // Witnesses can be empty because the 0x00 var int is not stored in content.
-            write!(f, "]")?;
-        }
+impl fmt::Debug for DebugElements<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_list().entries(self.0.iter().map(|elem| elem.as_hex())).finish()
     }
-
-    f.write_str(" }")
 }
 
-fn fmt_debug_pretty(w: &Witness, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-    f.write_str("Witness: {\n")?;
-    writeln!(f, "    indices: {},", w.witness_elements)?;
-    writeln!(f, "    indices_start: {},", w.indices_start)?;
-    f.write_str("    witnesses: [\n")?;
-
-    for instruction in w.iter() {
-        f.write_str("        [")?;
-        for (j, byte) in instruction.iter().enumerate() {
-            if j > 0 {
-                f.write_str(", ")?;
-            }
-            write!(f, "{:#04x}", byte)?;
-        }
-        f.write_str("],\n")?;
-    }
+/// Debug implementation that displays the witness as a structured output containing:
+/// - Number of witness elements
+/// - Total bytes across all elements
+/// - List of hex-encoded witness elements
+impl fmt::Debug for Witness {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let total_bytes: usize = self.iter().map(|elem| elem.len()).sum();
 
-    writeln!(f, "    ],")?;
-    writeln!(f, "}}")
+        f.debug_struct("Witness")
+            .field("num_elements", &self.witness_elements)
+            .field("total_bytes", &total_bytes)
+            .field("elements", &DebugElements(self))
+            .finish()
+    }
 }
 
 /// An iterator returning individual witness elements.


Test patch: