TL;DR
We’re looking to stabilize the debug builder APIs in the near future, so it’s time to make sure we’re happy with the functionality. Speak now or forever hold your peace.
Background
The debug builder APIs are part of the implementation of RFC 640: Debug Improvements. It proposed that the # “alternate” flag would be treated by fmt::Debug implementations as a request to “pretty print” output - turning
{ "foo": ComplexType { thing: Some(BufferedReader { reader: FileStream { path: "/home/sfackler/rust/README.md", mode: R }, buffer: 1013/65536 }), other_thing: 100 }, "bar": ComplexType { thing: Some(BufferedReader { reader: FileStream { path: "/tmp/foobar", mode: R }, buffer: 0/65536 }), other_thing: 0 } }
into
{
"foo": ComplexType {
thing: Some(
BufferedReader {
reader: FileStream {
path: "/home/sfackler/rust/README.md",
mode: R
},
buffer: 1013/65536
}
),
other_thing: 100
},
"bar": ComplexType {
thing: Some(
BufferedReader {
reader: FileStream {
path: "/tmp/foobar",
mode: R
},
buffer: 0/65536
}
),
other_thing: 0
}
}
The information content is the same - the only changes present are newlines and whitespace.
It would be a huge pain to have to manually handle both output variants via manual write! calls in fmt::Debug implementations, so some builder-style types were added to handle the complications automatically. The builders are currently used by #[derive(Debug)] but are unstable so manual Debug implementations can’t use them in stable code.
The API
The entry points consist of a series of methods on the fmt::Formatter struct:
impl<'a> Formatter {
pub fn debug_struct<'b>(&'b mut self, name: &str) -> DebugStruct<'b, 'a> { ... }
pub fn debug_tuple<'b>(&'b mut self, name: &str) -> DebugTuple<'b, 'a> { ... }
pub fn debug_list<'b>(&'b mut self) -> DebugList<'b, 'a> { ... }
pub fn debug_set<'b>(&'b mut self) -> DebugSet<'b, 'a> { ... }
pub fn debug_map<'b>(&'b mut self) -> DebugMap<'b, 'a> { ... }
}
Each of these types defines a builder-style API:
impl<'a, 'b> DebugStruct<'a, 'b> {
pub fn field(self, name: &str, value: &fmt::Debug) -> DebugStruct<'a, 'b> { ... }
pub fn finish(self) -> fmt::Result { ... }
}
impl<'a, 'b> DebugTuple<'a, 'b> {
pub fn field(self, value: &fmt::Debug) -> DebugTuple<'a, 'b> { ... }
pub fn finish(self) -> fmt::Result { ... }
}
impl<'a, 'b> DebugList<'a, 'b> {
pub fn entry(self, value: &fmt::Debug) -> DebugList<'a, 'b> { ... }
pub fn finish(self) -> fmt::Result { ... }
}
impl<'a, 'b> DebugSet<'a, 'b> {
pub fn entry(self, value: &fmt::Debug) -> DebugSet<'a, 'b> { ... }
pub fn finish(self) -> fmt::Result { ... }
}
impl<'a, 'b> DebugMap<'a, 'b> {
pub fn entry(self, key: &fmt::Debug, value: &fmt::Debug) -> DebugMap<'a, 'b> { ... }
pub fn finish(self) -> fmt::Result { ... }
}
Each variant corresponds to a common output format:
-
DebugStruct:MyType { field1: value1, field2: value2 } -
DeubgTuple:MyType(value1, value2) -
DebugList:[value1, value2] -
DebugSet:{value1, value2} -
DebugMap:{key1: value1, key2: value2}
Common usage in a Debug implementation would typically look something like
impl fmt::Debug for MyType {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("MyType")
.field("field1", &self.field1)
.field("field2", &self.field2)
.finish()
}
}
Stabilization
An open PR proposes to stabilize these APIs as-is. Is there anything that we should change before that happens? Potential points of discussion that have been mentioned before:
- Should we use
debug_tuple_structandDebugTupleStructinstead ofdebug_tupleandDebugTuple? It’s technically more correct but adds verbosity. -
DebugStructandDebugTuplehave afieldmethod whileDebugList,DebugSetandDebugMaphave anentrymethod. Should they be unified? It’s inconsistent but does match to what we normally refer to the contents of those kinds of structures by. - Should the builder methods take
&mut selfinstead ofself? Taking self by value makes the API a bit more foolproof (you can’t add more fields/entries after callingfinishfor example) but does mean you have to reassign the builder when using it in more complex cases:
let mut builder = fmt.debug_struct("MyType");
if some_condition {
builder = builder.field("conditional_field", &self.field);
// vs simply
// builder.field("conditional_field", &self.field);
}
builder.finish()
On the other hand, it allows for a nice one liner when implementing Debug for common data structures:
impl<K, V, S> Debug for HashMap<K, V, S>
where K: Eq + Hash + Debug, V: Debug, S: HashState
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.iter().fold(f.debug_map(), |b, (k, v)| b.entry(k, v)).finish()
}
}
Thoughts?