Closed jmillikin closed 10 months ago
Seems like really all you need is a way to write into DebugStruct::field
etc directly. I believe the common solution to this is wrapping it in format_args!
:
impl fmt::Debug for FancyBitset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("FancyBitset ")?;
let mut dbg = f.debug_set();
for bit in 0..32 {
let mask: u32 = 1 << bit;
if self.0 & mask == 0 {
continue;
}
match FancyBitset(mask) {
FancyBitset::A => dbg.entry(format_args!("A")),
FancyBitset::B => dbg.entry(format_args!("B")),
FancyBitset::C => dbg.entry(format_args!("C")),
FancyBitset::D => dbg.entry(format_args!("D")),
FancyBitset::E => dbg.entry(format_args!("E")),
_ => dbg.entry(format_args!("{:#010X}", mask)),
};
}
dbg.finish()
}
}
I recognize the temptation to code-golf the simplified example code snippets, but it's probably better to think of them as gesturing toward a general shape of problem rather than trying to optimize them as-is.
I was just pointing out an alternative, not optimizing.
We briefly discussed this in the libs API meeting yesterday, and are on board with adding this as an unstable feature.
Feel free to open a tracking issue and open a PR to rust-lang/rust to add it as unstable.
Please add open questions for the method names (_with
, _fmt
, ..?) and for what to with DebugMap::entry
.
Thank you!
I should also note that we also think your first alternative, the closure wrapping type, might be good to have in the standard library (in addition to the new methods on Debug{Struct, etc.}), although we think it should both implement Display
and Debug
(with identical impls, just like fmt::Arguments
), and therefore have a different name.
I vaguely remember there was already another ACP for that, but I can't find it right now.
Thanks! Tracking issue is https://github.com/rust-lang/rust/issues/117729 and implementation PR (with your suggested changes) is https://github.com/rust-lang/rust/pull/117730.
I should also note that we also think your first alternative, the closure wrapping type, might be good to have in the standard library (in addition to the new methods on Debug{Struct, etc.}), although we think it should both implement
Display
andDebug
(with identical impls, just likefmt::Arguments
), and therefore have a different name.I vaguely remember there was already another ACP for that, but I can't find it right now.
Sounds good to me. I've included this in the implementation PR as fmt::FormatterFn
.
Proposal
Problem statement
Implementing
fmt::Debug
for FFI types can be difficult because their layouts often don't match Rust conventions, for example by using a bitset for boolean properties or a(tag, union)
pair instead of an enumeration.Currently the formatting of these values can require creating auxiliary wrapper types that exist just to
impl Debug
for one particular field, which is verbose and unergonomic.Motivating examples or use cases
Example one: a bitset representing a set of boolean options. The bits should be formatted with names, and unknown bits should be formatted as numbers.
Example two: a union field conceptually only has one of its sub-fields populated at any given time, but figuring out which sub-field is valid requires inspecting the tag.
Solution sketch
Add methods to
DebugStruct
,DebugList
,DebugSet
, andDebugTuple
that make it easy to embed a formatting closure function directly into the mainimpl Debug
of a type.I'm ignoring
DebugMap
, but if the libs team thinks it should have similar functionality then i suggestkey_with()
andvalue_with()
rather than trying to have a double-closureentry_with()
.Also, I'm not married to the names, so you'd prefer
field_fmt()
or whatever then that's fine.They would be used like this:
Alternatives
Closure-wrapper function
This is more generic because it lets a closure be used in any place a
&dyn Debug
is expected, but it's slightly less flexible (F: Fn
rather thanF: FnOnce
) and the call site is more verbose due to required type annotations.Extension trait
Mostly I just wanted to see if it was possible. It is, but it's horrible.
trait Debug
quite reasonably uses&self
receivers, so the only way to getFnOnce
is to play games with aCell<Option<F>>
Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution: