diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 19dd13967fdd6..de3518fc12ec5 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -99,6 +99,21 @@ pub(super) fn debug_struct_new<'a, 'b>( DebugStruct { fmt, result, has_fields: false } } +fn debug_struct_field_non_pretty( + fmt: &mut fmt::Formatter<'_>, + prefix: &str, + name: &str, + value_fmt: F, +) -> fmt::Result +where + F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result, +{ + fmt.write_str(prefix)?; + fmt.write_str(name)?; + fmt.write_str(": ")?; + value_fmt(fmt) +} + impl<'a, 'b: 'a> DebugStruct<'a, 'b> { /// Adds a new field to the generated struct output. /// @@ -156,10 +171,7 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { writer.write_str(",\n") } else { let prefix = if self.has_fields { ", " } else { " { " }; - self.fmt.write_str(prefix)?; - self.fmt.write_str(name)?; - self.fmt.write_str(": ")?; - value_fmt(self.fmt) + debug_struct_field_non_pretty(self.fmt, prefix, name, value_fmt) } }); @@ -167,6 +179,21 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { self } + /// Adds a new field to the generated struct output in non-pretty mode. + /// + /// Fast path version of [`Self::field`] that allows skipping the pretty mode check. + /// It's the caller's responsibility to verify non-pretty mode is configured + /// prior to calling this function. + pub(super) fn field_non_pretty(&mut self, name: &str, value: &dyn fmt::Debug) -> &mut Self { + self.result = self.result.and_then(|_| { + let prefix = if self.has_fields { ", " } else { " { " }; + debug_struct_field_non_pretty(self.fmt, prefix, name, |f| value.fmt(f)) + }); + + self.has_fields = true; + self + } + /// Marks the struct as non-exhaustive, indicating to the reader that there are some other /// fields that are not shown in the debug representation. /// @@ -250,6 +277,18 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { self.result } + /// Finishes output in non-pretty mode and returns any error encountered. + /// + /// Fast path version of [`Self::finish`] that allows skipping the pretty mode check. + /// It's the caller's responsibility to verify non-pretty mode is configured + /// prior to calling this function. + pub(super) fn finish_non_pretty(&mut self) -> fmt::Result { + if self.has_fields { + self.result = self.result.and_then(|_| self.fmt.write_str(" }")); + } + self.result + } + fn is_pretty(&self) -> bool { self.fmt.alternate() } diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 4d000691b8b5f..5228775f8e9ef 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -564,6 +564,27 @@ pub struct Formatter<'a> { buf: &'a mut (dyn Write + 'a), } +// The fixed-arity derive helpers check `$formatter.alternate()` ("pretty mode") once +// so that in the common case (non-alternate mode) we can use the fast path +// that does not require re-checking it once for every field. +macro_rules! debug_struct_fixed_fields { + ( + $formatter:ident, + $name:expr, + $(($field_name:expr, $value:expr)),+ $(,)? + ) => {{ + if $formatter.alternate() { + let mut builder = builders::debug_struct_new($formatter, $name); + $(builder.field($field_name, $value);)+ + builder.finish() + } else { + let mut builder = builders::debug_struct_new($formatter, $name); + $(builder.field_non_pretty($field_name, $value);)+ + builder.finish_non_pretty() + } + }}; +} + impl<'a> Formatter<'a> { /// Creates a new formatter with given [`FormattingOptions`]. /// @@ -2460,9 +2481,7 @@ impl<'a> Formatter<'a> { name1: &str, value1: &dyn Debug, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.finish() + debug_struct_fixed_fields!(self, name, (name1, value1)) } /// Shrinks `derive(Debug)` code, for faster compilation and smaller @@ -2478,10 +2497,7 @@ impl<'a> Formatter<'a> { name2: &str, value2: &dyn Debug, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); - builder.finish() + debug_struct_fixed_fields!(self, name, (name1, value1), (name2, value2)) } /// Shrinks `derive(Debug)` code, for faster compilation and smaller @@ -2499,11 +2515,7 @@ impl<'a> Formatter<'a> { name3: &str, value3: &dyn Debug, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); - builder.field(name3, value3); - builder.finish() + debug_struct_fixed_fields!(self, name, (name1, value1), (name2, value2), (name3, value3)) } /// Shrinks `derive(Debug)` code, for faster compilation and smaller @@ -2523,12 +2535,14 @@ impl<'a> Formatter<'a> { name4: &str, value4: &dyn Debug, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); - builder.field(name3, value3); - builder.field(name4, value4); - builder.finish() + debug_struct_fixed_fields!( + self, + name, + (name1, value1), + (name2, value2), + (name3, value3), + (name4, value4), + ) } /// Shrinks `derive(Debug)` code, for faster compilation and smaller @@ -2550,13 +2564,15 @@ impl<'a> Formatter<'a> { name5: &str, value5: &dyn Debug, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); - builder.field(name3, value3); - builder.field(name4, value4); - builder.field(name5, value5); - builder.finish() + debug_struct_fixed_fields!( + self, + name, + (name1, value1), + (name2, value2), + (name3, value3), + (name4, value4), + (name5, value5), + ) } /// Shrinks `derive(Debug)` code, for faster compilation and smaller binaries. diff --git a/library/coretests/tests/fmt/builders.rs b/library/coretests/tests/fmt/builders.rs index 156eebb1e9d2e..9f73ce875ca49 100644 --- a/library/coretests/tests/fmt/builders.rs +++ b/library/coretests/tests/fmt/builders.rs @@ -188,6 +188,42 @@ mod debug_struct { assert_eq!("Foo {\n bar: true,\n baz: false,\n}", format!("{Foo:#?}")) } + + #[test] + fn test_derived_helpers_match_builder() { + fn assert_matches_builder(builder: &dyn fmt::Debug, derived: &dyn fmt::Debug) { + assert_eq!(format!("{builder:?}"), format!("{derived:?}")); + assert_eq!(format!("{builder:#?}"), format!("{derived:#?}")); + } + + macro_rules! check_arity { + ($name:ident { $($field:ident: $value:expr),+ $(,)? }) => {{ + #[derive(Debug)] + struct $name { + $($field: u8),+ + } + + struct ViaBuilder<'a>(&'a $name); + + impl fmt::Debug for ViaBuilder<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($name)) + $(.field(stringify!($field), &self.0.$field))+ + .finish() + } + } + + let value = $name { $($field: $value),+ }; + assert_matches_builder(&ViaBuilder(&value), &value); + }}; + } + + check_arity!(One { a: 1 }); + check_arity!(Two { a: 1, b: 2 }); + check_arity!(Three { a: 1, b: 2, c: 3 }); + check_arity!(Four { a: 1, b: 2, c: 3, d: 4 }); + check_arity!(Five { a: 1, b: 2, c: 3, d: 4, e: 5 }); + } } mod debug_tuple {