Summary

Add two new methods to std::fmt::DebugMap for writing the key and value part of a map entry

項目
separately:

#![allow(unused)] fn main() { impl<'a, 'b: 'a> DebugMap<'a, 'b> { pub fn key(&mut self, key: &dyn Debug) -> &mut Self; pub fn value(&mut self, value: &dyn Debug) -> &mut Self; } }

Motivation

The format builders available to std::fmt::Debug implementations

実装
through the std::fmt::Formatter help keep the textual debug representation
表現
of Rust structures consistent.
構成される
They're also convenient to use and make sure the various
さまざまな
formatting flags are retained when formatting entries.
項目
The standard formatting API in std::fmt is similar
似ている、同様の
to serde::ser:

  • Debug -> Serialize
  • Formatter -> Serializer
  • DebugMap -> SerializeMap
  • DebugList -> SerializeSeq
  • DebugTuple -> SerializeTuple / SerializeTupleStruct / SerilizeTupleVariant
  • DebugStruct -> SerializeStruct / SerializeStructVariant

There's one notable inconsistency though: an implementation

実装
of SerializeMap must support serializing its keys and values independently. This isn't supported by DebugMap because its entry
項目
method takes
とる
both a key and a value together. That means it's not possible to write a Serializer that defers entirely to the format builders.

Adding

たす
separate key and value methods to DebugMap will align
揃える
it more closely with SerializeMap, and make it possible to build a Serializer based
基となる、基底(の)
on the standard format builders.

Guide-level explanation

In DebugMap, an entry

項目
is the pair of a key and a value. That means the following Debug implementation:
実装

#![allow(unused)] fn main() { use std::fmt; struct Map; impl fmt::Debug for Map { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut map = f.debug_map(); map.entry(&"key", &"value"); map.finish() } } }

is equivalent

等価
to:

#![allow(unused)] fn main() { impl fmt::Debug for Map { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut map = f.debug_map(); // Equivalent to map.entry map.key(&"key").value(&"value"); map.finish() } } }

Every call

呼び出し
to key must be directly
直接
followed
下記の、次に続く、追従する
by a corresponding
照応する
call
呼び出し
to value to complete
完全な
the entry:
項目

#![allow(unused)] fn main() { impl fmt::Debug for Map { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut map = f.debug_map(); map.key(&1); // err: attempt to start a new entry without finishing the current one map.key(&2); map.finish() } } }

key must be called

呼び出し
before value:

#![allow(unused)] fn main() { impl fmt::Debug for Map { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut map = f.debug_map(); // err: attempt to write a value without first writing its key map.value(&"value"); map.key(&"key"); map.finish() } } }

Each entry

項目
must be finished before the map can be finished:

#![allow(unused)] fn main() { impl fmt::Debug for Map { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut map = f.debug_map(); map.key(&1); // err: attempt to finish a map that has an incomplete key map.finish() } } }

Any incorrect calls

呼び出し
to key and value will panic.

When to use key and value

Why would you want to use key and value directly

直接
if they're less convenient than entry
項目
? The reason is when the driver of the DebugMap is a framework like serde rather than a data structure directly:
直接

#![allow(unused)] fn main() { struct DebugMap<'a, 'b: 'a>(fmt::DebugMap<'a, 'b>); impl<'a, 'b: 'a> SerializeMap for DebugMap<'a, 'b> { type Ok = (); type Error = Error; fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error> where T: Serialize, { self.0.key(&key.to_debug()); Ok(()) } fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.0.value(&value.to_debug()); Ok(()) } fn serialize_entry<K: ?Sized, V: ?Sized>( &mut self, key: &K, value: &V, ) -> Result<(), Self::Error> where K: Serialize, V: Serialize, { self.0.entry(&key.to_debug(), &value.to_debug()); Ok(()) } fn end(self) -> Result<Self::Ok, Self::Error> { self.0.finish().map_err(Into::into) } } }

Consumers should prefer calling

呼び出し
entry
項目
over key and value.

Reference-level explanation

The key and value methods can be implemented

実装する
on DebugMap by tracking the state of the current entry
項目
in a bool, and splitting the existing entry
項目
method into two:

#![allow(unused)] fn main() { pub struct DebugMap<'a, 'b: 'a> { has_key: bool, .. } pub fn debug_map_new<'a, 'b>(fmt: &'a mut fmt::Formatter<'b>) -> DebugMap<'a, 'b> { DebugMap { has_key: false, .. } } impl<'a, 'b: 'a> DebugMap<'a, 'b> { pub fn entry(&mut self, key: &dyn fmt::Debug, value: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { self.key(key).value(value) } pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { // Make sure there isn't a partial entry assert!(!self.has_key, "attempted to begin a new map entry without completing the previous one"); self.result = self.result.and_then(|_| { // write the key // Mark that we're in an entry self.has_key = true; Ok(()) }); self } pub fn value(&mut self, value: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { // Make sure there is a partial entry to finish assert!(self.has_key, "attempted to format a map value before its key"); self.result = self.result.and_then(|_| { // write the value // Mark that we're not in an entry self.has_key = false; Ok(()) }); self.has_fields = true; self } pub fn finish(&mut self) -> fmt::Result { // Make sure there isn't a partial entry assert!(!self.has_key, "attempted to finish a map with a partial entry"); self.result.and_then(|_| self.fmt.write_str("}")) } } }

Drawbacks

The proposed key and value methods are't immediately

直後に、直接的に
useful for Debug implementors that are able to call
呼び出し
entry
項目
instead. This creates a decision point where there wasn't one before. The proposed implementation
実装
is also going to be less efficient
効率のよい
than the one that exists now because it introduces a few conditionals.
条件付き、条件的

On balance, the additional

追加の
key and value methods are a small and unsurprising addition
追加
that enables a set
セットする、集合
of use-cases that weren't possible before, and aligns more closely with serde.

Rationale and alternatives
代わりのもの、選択肢

The universal alternative

代わりのもの、選択肢
of simply not doing this leaves consumers that do need to format map keys independently of values with a few options:

  • Write an alternative
    代わりのもの、選択肢
    implementation
    実装
    of the format builders. The output from this alternative
    代わりのもの、選択肢
    implementation
    実装
    would need to be kept reasonably in-sync with the one in the standard library. It doesn't change very frequently, but does from time to time. It would also have to take
    とる
    the same care as the standard library implementation
    実装
    to retain formatting flags when working with entries.
    項目
  • Buffer keys and format them together with values when the whole entry
    項目
    is available. Unless the key is guaranteed
    保証する
    to live until the value is supplied (meaning it probably needs to be 'static) then the key will need to be formatted into a string first. This means allocating
    確保する
    (though the cost could be amortized over the whole map) and potentially losing formatting flags when buffering.

Another alternative

代わりのもの、選択肢
is to avoid
避ける、回避する
panicking if the sequence
連なり、並び
of entries
項目
doesn't follow
下記の、次に続く、追従する
the expected pattern of key then value. Instead, DebugMap could make a best-effort attempt to represent keys without values and values without keys. However, this approach has the drawback of masking
マスク、隠す
incorrect Debug implementations,
実装
may produce
産出する
a surprising output and doesn't reduce the complexity of the implementation
実装
(we'd still need to tell whether a key should be followed
下記の、次に続く、追従する
by a : separator or a , ).

Prior art

The serde::ser::SerializeMap API (and libserialize::Encoder for what it's worth) requires map keys and values can be serialized independently. SerializeMap provides

与える
a serialize_entry method, which is similar
似ている、同様の
to the existing DebugMap::entry, but is only supposed to be used as an optimization.

Unresolved questions

Future possibilities

The internal implementation

実装
could optimize
最適化する
the entry
項目
method to avoid
避ける、回避する
a few redundant checks.