From 192c29c764cfad43069f46a5a92b5ca0a7e9f4ac Mon Sep 17 00:00:00 2001 From: panstromek Date: Thu, 22 Oct 2026 07:58:25 +0200 Subject: [PATCH 01/15] perf: emit field names as a concatenated string in derive(Debug) for large structs This improves compile time for crates that derive Debug for large structs (e.g., ripgrep). It also reduces metadata size for some of those. --- .../src/deriving/debug.rs | 20 +++++++++---------- library/core/src/fmt/mod.rs | 10 ++++++---- tests/ui/derives/deriving-all-codegen.stdout | 3 +-- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 0be5f36de980b..2295faae68a78 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -140,29 +140,29 @@ fn show_substructure( BlockOrExpr::new_expr(expr) } else { // Enough fields that we must use the any-length method. - let mut name_exprs = ThinVec::with_capacity(fields.len()); + let mut names = String::new(); let mut value_exprs = ThinVec::with_capacity(fields.len()); + let fields_len = fields.len(); for (i, field) in fields.into_iter().enumerate() { if is_struct { - name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name)); + names.push_str(field.name.unwrap().name.as_str()); + let is_last = i == fields_len - 1; + if !is_last { + names.push(' '); + } } let field = expr_for_field(field, i); value_exprs.push(field); } - // `let names: &'static _ = &["field1", "field2"];` + // `let names: &'static _ = "field1 field2"` let names_let = is_struct.then(|| { let lt_static = Some(cx.lifetime_static(span)); let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not); - cx.stmt_let_ty( - span, - false, - sym::names, - Some(ty_static_ref), - cx.expr_array_ref(span, name_exprs), - ) + let names_expr = cx.expr_str(span, Symbol::intern(&names)); + cx.stmt_let_ty(span, false, sym::names, Some(ty_static_ref), names_expr) }); // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];` diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index a9af7e00b1d5f..09517d7ffa029 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -9,7 +9,7 @@ use crate::marker::{PhantomData, PointeeSized}; use crate::num::imp::fmt as numfmt; use crate::ops::Deref; use crate::ptr::NonNull; -use crate::{iter, mem, result, str}; +use crate::{mem, result, str}; mod builders; #[cfg(not(no_fp_fmt_parse))] @@ -2603,17 +2603,19 @@ impl<'a> Formatter<'a> { /// Shrinks `derive(Debug)` code, for faster compilation and smaller binaries. /// For the cases not covered by `debug_struct_field[12345]_finish`. + /// Field names should be separated by space. #[doc(hidden)] #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_fields_finish<'b>( &'b mut self, name: &str, - names: &[&str], + names: &str, values: &[&dyn Debug], ) -> Result { - assert_eq!(names.len(), values.len()); + let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, name); - for (name, value) in iter::zip(names, values) { + for value in values.into_iter() { + let name = names.next().unwrap(); builder.field(name, value); } builder.finish() diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 94603a91547c1..0bcce4b96d236 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -422,8 +422,7 @@ impl ::core::marker::Copy for Big { } impl ::core::fmt::Debug for Big { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - let names: &'static _ = - &["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8"]; + let names: &'static _ = "b1 b2 b3 b4 b5 b6 b7 b8"; let values: &[&dyn ::core::fmt::Debug] = &[&self.b1, &self.b2, &self.b3, &self.b4, &self.b5, &self.b6, &self.b7, &&self.b8]; From bea7af9f13fe2373e8fcf4a315c2b06b3240d21d Mon Sep 17 00:00:00 2001 From: panstromek Date: Thu, 22 Oct 2026 10:32:32 +0200 Subject: [PATCH 02/15] Add test for debug output for big struct --- tests/ui/derives/debug/derive-debug.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/ui/derives/debug/derive-debug.rs b/tests/ui/derives/debug/derive-debug.rs index e4e377dd90d13..63ccf8457e8c5 100644 --- a/tests/ui/derives/debug/derive-debug.rs +++ b/tests/ui/derives/debug/derive-debug.rs @@ -9,6 +9,16 @@ struct Tuple(isize, usize); #[derive(Debug)] struct Struct { x: isize, y: usize } +#[derive(Debug)] +struct BigStruct { + a: isize, + best: usize, + c: u64, + d: u32, + estimate: u8, + f: i8 +} + #[derive(Debug)] enum Enum { Nullary, @@ -29,6 +39,8 @@ pub fn main() { t!(Unit, "Unit"); t!(Tuple(1, 2), "Tuple(1, 2)"); t!(Struct { x: 1, y: 2 }, "Struct { x: 1, y: 2 }"); + t!(BigStruct { a: 1, best: 2, c: 3, d: 4, estimate: 5, f: 6 }, + "BigStruct { a: 1, best: 2, c: 3, d: 4, estimate: 5, f: 6 }"); t!(Enum::Nullary, "Nullary"); t!(Enum::Variant(1, 2), "Variant(1, 2)"); t!(Enum::StructVariant { x: 1, y: 2 }, "StructVariant { x: 1, y: 2 }"); From 194a9a57d407ffd905a3b4ed729bbdcf50612d4c Mon Sep 17 00:00:00 2001 From: panstromek Date: Thu, 22 Oct 2026 14:58:07 +0200 Subject: [PATCH 03/15] Use string contatenation strategy for fieldN helpers as well --- .../src/deriving/debug.rs | 22 ++++-- library/core/src/fmt/mod.rs | 74 ++++++++----------- tests/ui/derives/deriving-all-codegen.stdout | 22 +++--- 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 2295faae68a78..3e9e94f4f9758 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -94,7 +94,7 @@ fn show_substructure( (false, 0) } ast::VariantData::Tuple(..) => (false, 1), - ast::VariantData::Struct { .. } => (true, 2), + ast::VariantData::Struct { .. } => (true, 1), }; // The number of fields that can be handled without an array. @@ -125,17 +125,29 @@ fn show_substructure( }; let fn_path_debug = cx.std_path(span, &[sym::fmt, sym::Formatter, Symbol::intern(&debug)]); - let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field); - args.extend([fmt, name]); + let mut args = ThinVec::with_capacity(3 + fields.len() * args_per_field); + args.extend([fmt]); + + let mut names = String::new(); + if is_struct { + names.push_str(ident.name.as_str()) + } else { + args.push(name); + }; + for (i, field) in fields.into_iter().enumerate() { if is_struct { - let name = cx.expr_str(field.span, field.name.unwrap().name); - args.push(name); + names.push(' '); + names.push_str(field.name.unwrap().name.as_str()); } let field = expr_for_field(field, i); args.push(field); } + if is_struct { + let name_expr = cx.expr_str(span, Symbol::intern(&names)); + args.push(name_expr); + } let expr = cx.expr_call_global(span, fn_path_debug, args); BlockOrExpr::new_expr(expr) } else { diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 09517d7ffa029..b3c35e361f885 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -2496,14 +2496,10 @@ impl<'a> Formatter<'a> { /// faster for 1 field. #[doc(hidden)] #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] - pub fn debug_struct_field1_finish<'b>( - &'b mut self, - name: &str, - name1: &str, - value1: &dyn Debug, - ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); + pub fn debug_struct_field1_finish<'b>(&'b mut self, value1: &dyn Debug, names: &str) -> Result { + let mut names = names.split(' '); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); + builder.field(names.next().unwrap(), value1); builder.finish() } @@ -2514,15 +2510,14 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field2_finish<'b>( &'b mut self, - name: &str, - name1: &str, value1: &dyn Debug, - name2: &str, value2: &dyn Debug, + names: &str, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); + let mut names = names.split(' '); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); + builder.field(names.next().unwrap(), value1); + builder.field(names.next().unwrap(), value2); builder.finish() } @@ -2533,18 +2528,16 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field3_finish<'b>( &'b mut self, - name: &str, - name1: &str, value1: &dyn Debug, - name2: &str, value2: &dyn Debug, - name3: &str, value3: &dyn Debug, + names: &str, ) -> Result { - let mut builder = builders::debug_struct_new(self, name); - builder.field(name1, value1); - builder.field(name2, value2); - builder.field(name3, value3); + let mut names = names.split(' '); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); + builder.field(names.next().unwrap(), value1); + builder.field(names.next().unwrap(), value2); + builder.field(names.next().unwrap(), value3); builder.finish() } @@ -2555,21 +2548,18 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field4_finish<'b>( &'b mut self, - name: &str, - name1: &str, value1: &dyn Debug, - name2: &str, value2: &dyn Debug, - name3: &str, value3: &dyn Debug, - name4: &str, value4: &dyn Debug, + names: &str, ) -> 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); + let mut names = names.split(' '); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); + builder.field(names.next().unwrap(), value1); + builder.field(names.next().unwrap(), value2); + builder.field(names.next().unwrap(), value3); + builder.field(names.next().unwrap(), value4); builder.finish() } @@ -2580,24 +2570,20 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field5_finish<'b>( &'b mut self, - name: &str, - name1: &str, value1: &dyn Debug, - name2: &str, value2: &dyn Debug, - name3: &str, value3: &dyn Debug, - name4: &str, value4: &dyn Debug, - name5: &str, value5: &dyn Debug, + names: &str, ) -> 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); + let mut names = names.split(' '); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); + builder.field(names.next().unwrap(), value1); + builder.field(names.next().unwrap(), value2); + builder.field(names.next().unwrap(), value3); + builder.field(names.next().unwrap(), value4); + builder.field(names.next().unwrap(), value5); builder.finish() } diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 0bcce4b96d236..7655f7211140d 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -101,8 +101,8 @@ impl ::core::marker::Copy for Point { } impl ::core::fmt::Debug for Point { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field2_finish(f, "Point", "x", - &self.x, "y", &&self.y) + ::core::fmt::Formatter::debug_struct_field2_finish(f, &self.x, + &&self.y, "Point x y") } } #[automatically_derived] @@ -185,8 +185,8 @@ impl ::core::marker::Copy for PackedPoint { } impl ::core::fmt::Debug for PackedPoint { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field2_finish(f, "PackedPoint", - "x", &{ self.x }, "y", &&{ self.y }) + ::core::fmt::Formatter::debug_struct_field2_finish(f, &{ self.x }, + &&{ self.y }, "PackedPoint x y") } } #[automatically_derived] @@ -338,8 +338,8 @@ impl ::core::marker::Copy for SingleField { } impl ::core::fmt::Debug for SingleField { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field1_finish(f, "SingleField", - "foo", &&self.foo) + ::core::fmt::Formatter::debug_struct_field1_finish(f, &&self.foo, + "SingleField foo") } } #[automatically_derived] @@ -750,8 +750,8 @@ impl ::core::fmt::Debug for Generic where T::A: ::core::fmt::Debug { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field3_finish(f, "Generic", "t", - &self.t, "ta", &self.ta, "u", &&self.u) + ::core::fmt::Formatter::debug_struct_field3_finish(f, &self.t, + &self.ta, &&self.u, "Generic t ta u") } } #[automatically_derived] @@ -1039,7 +1039,7 @@ impl ::core::fmt::Debug for Enum1 { match self { Self::Single { x: __self_0 } => ::core::fmt::Formatter::debug_struct_field1_finish(f, - "Single", "x", &__self_0), + &__self_0, "Single x"), } } } @@ -1305,8 +1305,8 @@ impl ::core::fmt::Debug for Mixed { ::core::fmt::Formatter::debug_tuple_field1_finish(f, "R", &__self_0), Self::S { d1: __self_0, d2: __self_1 } => - ::core::fmt::Formatter::debug_struct_field2_finish(f, "S", - "d1", __self_0, "d2", &__self_1), + ::core::fmt::Formatter::debug_struct_field2_finish(f, + __self_0, &__self_1, "S d1 d2"), } } } From db15c6aed44c7965da8d106e6f847e141e725387 Mon Sep 17 00:00:00 2001 From: panstromek Date: Thu, 22 Oct 2026 15:12:51 +0200 Subject: [PATCH 04/15] Concat struct name together with field names This avoids one more string and makes the derive code slightly nicer as we don't have to track whether we're on last item --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 12 ++++++------ library/core/src/fmt/mod.rs | 3 +-- tests/ui/derives/deriving-all-codegen.stdout | 5 ++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 3e9e94f4f9758..a40f9e4f100b0 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -153,16 +153,15 @@ fn show_substructure( } else { // Enough fields that we must use the any-length method. let mut names = String::new(); + if is_struct { + names.push_str(ident.name.as_str()); + } let mut value_exprs = ThinVec::with_capacity(fields.len()); - let fields_len = fields.len(); for (i, field) in fields.into_iter().enumerate() { if is_struct { + names.push(' '); names.push_str(field.name.unwrap().name.as_str()); - let is_last = i == fields_len - 1; - if !is_last { - names.push(' '); - } } let field = expr_for_field(field, i); @@ -209,9 +208,10 @@ fn show_substructure( let mut args = ThinVec::with_capacity(4); args.push(fmt); - args.push(name); if is_struct { args.push(cx.expr_name(span, sym::names)); + } else { + args.push(name); } args.push(cx.expr_name(span, sym::values)); let expr = cx.expr_call_global(span, fn_path_debug_internal, args); diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index b3c35e361f885..394d49321d0bf 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -2594,12 +2594,11 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_fields_finish<'b>( &'b mut self, - name: &str, names: &str, values: &[&dyn Debug], ) -> Result { let mut names = names.split(' '); - let mut builder = builders::debug_struct_new(self, name); + let mut builder = builders::debug_struct_new(self, names.next().unwrap()); for value in values.into_iter() { let name = names.next().unwrap(); builder.field(name, value); diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 7655f7211140d..1adf6ff3482a3 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -422,12 +422,11 @@ impl ::core::marker::Copy for Big { } impl ::core::fmt::Debug for Big { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - let names: &'static _ = "b1 b2 b3 b4 b5 b6 b7 b8"; + let names: &'static _ = "Big b1 b2 b3 b4 b5 b6 b7 b8"; let values: &[&dyn ::core::fmt::Debug] = &[&self.b1, &self.b2, &self.b3, &self.b4, &self.b5, &self.b6, &self.b7, &&self.b8]; - ::core::fmt::Formatter::debug_struct_fields_finish(f, "Big", names, - values) + ::core::fmt::Formatter::debug_struct_fields_finish(f, names, values) } } #[automatically_derived] From b5408528f90534857ff2c1d5504d85a927438c7b Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:15:12 +0200 Subject: [PATCH 05/15] doc: cleanup comment --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index a40f9e4f100b0..d9919ac9e2180 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -168,7 +168,7 @@ fn show_substructure( value_exprs.push(field); } - // `let names: &'static _ = "field1 field2"` + // `let names: &'static _ = "StructName field1 field2"` let names_let = is_struct.then(|| { let lt_static = Some(cx.lifetime_static(span)); let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not); From f3f485e00f2fec15e3d064a7062ef3dd9a66606d Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:16:09 +0200 Subject: [PATCH 06/15] doc: cleanup comment --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index d9919ac9e2180..85bb6a993f04d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -197,7 +197,7 @@ fn show_substructure( cx.expr_array_ref(span, value_exprs), ); - // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or + // `fmt::Formatter::debug_struct_fields_finish(fmt, names, values)` or // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)` let sym_debug = if is_struct { sym::debug_struct_fields_finish From 635700c03b95c69af1bdb44fb566c379e2a6c712 Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:17:27 +0200 Subject: [PATCH 07/15] perf: shrink unnecessary capacity --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 85bb6a993f04d..efe0da17f4a87 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -206,7 +206,7 @@ fn show_substructure( }; let fn_path_debug_internal = cx.std_path(span, &[sym::fmt, sym::Formatter, sym_debug]); - let mut args = ThinVec::with_capacity(4); + let mut args = ThinVec::with_capacity(3); args.push(fmt); if is_struct { args.push(cx.expr_name(span, sym::names)); From 9e5308ce4fb1207fed8d27d6853e8ee98c0fe381 Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:21:28 +0200 Subject: [PATCH 08/15] ref: use thin_vec! macro avoid managing the capacity manually --- .../rustc_builtin_macros/src/deriving/debug.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index efe0da17f4a87..0495d28943896 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -206,14 +206,12 @@ fn show_substructure( }; let fn_path_debug_internal = cx.std_path(span, &[sym::fmt, sym::Formatter, sym_debug]); - let mut args = ThinVec::with_capacity(3); - args.push(fmt); - if is_struct { - args.push(cx.expr_name(span, sym::names)); - } else { - args.push(name); - } - args.push(cx.expr_name(span, sym::values)); + let args = thin_vec![ + fmt, + if is_struct { cx.expr_name(span, sym::names) } else { name }, + cx.expr_name(span, sym::values) + ]; + let expr = cx.expr_call_global(span, fn_path_debug_internal, args); let mut stmts = ThinVec::with_capacity(2); From 9e327792cc89d388ce58d7567e3dfc3cb475615c Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:24:48 +0200 Subject: [PATCH 09/15] perf: avoid allocating more capacity for tuples --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 0495d28943896..8b0a490d736d3 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -214,11 +214,12 @@ fn show_substructure( let expr = cx.expr_call_global(span, fn_path_debug_internal, args); - let mut stmts = ThinVec::with_capacity(2); - if is_struct { - stmts.push(names_let.unwrap()); - } - stmts.push(values_let); + let stmts = if is_struct { + thin_vec![names_let.unwrap(), values_let] + } else { + thin_vec![values_let] + }; + BlockOrExpr::new_mixed(stmts, Some(expr)) } } From 88f395a92e6a1ef84ad5744dcdfca6cda933d5bb Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:32:44 +0200 Subject: [PATCH 10/15] perf: reduce unnecessary capacity --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 8b0a490d736d3..c2b6205106860 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -125,7 +125,7 @@ fn show_substructure( }; let fn_path_debug = cx.std_path(span, &[sym::fmt, sym::Formatter, Symbol::intern(&debug)]); - let mut args = ThinVec::with_capacity(3 + fields.len() * args_per_field); + let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field); args.extend([fmt]); let mut names = String::new(); From 4526f83e6a161051c923afa754ab3a55f65f264f Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:34:18 +0200 Subject: [PATCH 11/15] cln: remove args_per_field, it's now always 1 in place where it gets used --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index c2b6205106860..d41a343b9aa2c 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -87,14 +87,14 @@ fn show_substructure( // Struct and tuples are similar enough that we use the same code for both, // with some extra pieces for structs due to the field names. - let (is_struct, args_per_field) = match vdata { + let is_struct = match vdata { ast::VariantData::Unit(..) => { // Special fast path for unit variants. assert!(fields.is_empty()); - (false, 0) + false } - ast::VariantData::Tuple(..) => (false, 1), - ast::VariantData::Struct { .. } => (true, 1), + ast::VariantData::Tuple(..) => false, + ast::VariantData::Struct { .. } => true, }; // The number of fields that can be handled without an array. @@ -125,7 +125,7 @@ fn show_substructure( }; let fn_path_debug = cx.std_path(span, &[sym::fmt, sym::Formatter, Symbol::intern(&debug)]); - let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field); + let mut args = ThinVec::with_capacity(2 + fields.len()); args.extend([fmt]); let mut names = String::new(); From 9dcaa689851d01aa092016417fbea80ef74b6b09 Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:46:30 +0200 Subject: [PATCH 12/15] ref: rename --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index d41a343b9aa2c..88f04730c3bd3 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -75,13 +75,13 @@ fn show_substructure( _ => cx.dcx().span_bug(span, "unexpected substructure in `derive(Debug)`"), }; - let name = cx.expr_str(span, ident.name); + let expr_name = cx.expr_str(span, ident.name); let fmt = cx.expr_name(span, sym::f); // Fieldless enums have been special-cased earlier if fmt_detail == FmtDebug::Shallow { let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]); - let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]); + let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name]); return BlockOrExpr::new_expr(expr); } @@ -114,7 +114,7 @@ fn show_substructure( if fields.is_empty() { // Special case for no fields. let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]); - let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]); + let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name]); BlockOrExpr::new_expr(expr) } else if fields.len() <= CUTOFF { // Few enough fields that we can use a specific-length method. @@ -132,7 +132,7 @@ fn show_substructure( if is_struct { names.push_str(ident.name.as_str()) } else { - args.push(name); + args.push(expr_name); }; for (i, field) in fields.into_iter().enumerate() { @@ -208,7 +208,7 @@ fn show_substructure( let args = thin_vec![ fmt, - if is_struct { cx.expr_name(span, sym::names) } else { name }, + if is_struct { cx.expr_name(span, sym::names) } else { expr_name }, cx.expr_name(span, sym::values) ]; From b99c49b3a243aa504c3c4453e68c15632a83bb47 Mon Sep 17 00:00:00 2001 From: panstromek Date: Fri, 23 Oct 2026 15:49:46 +0200 Subject: [PATCH 13/15] perf: avoid allocating expression for name for cases where it's not used We do this by changing the variable to a closure and only call it when it's needed --- compiler/rustc_builtin_macros/src/deriving/debug.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 88f04730c3bd3..f74749ac28b0d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -75,13 +75,13 @@ fn show_substructure( _ => cx.dcx().span_bug(span, "unexpected substructure in `derive(Debug)`"), }; - let expr_name = cx.expr_str(span, ident.name); + let expr_name = || cx.expr_str(span, ident.name); let fmt = cx.expr_name(span, sym::f); // Fieldless enums have been special-cased earlier if fmt_detail == FmtDebug::Shallow { let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]); - let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name]); + let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name()]); return BlockOrExpr::new_expr(expr); } @@ -114,7 +114,7 @@ fn show_substructure( if fields.is_empty() { // Special case for no fields. let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]); - let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name]); + let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, expr_name()]); BlockOrExpr::new_expr(expr) } else if fields.len() <= CUTOFF { // Few enough fields that we can use a specific-length method. @@ -132,7 +132,7 @@ fn show_substructure( if is_struct { names.push_str(ident.name.as_str()) } else { - args.push(expr_name); + args.push(expr_name()); }; for (i, field) in fields.into_iter().enumerate() { @@ -208,7 +208,7 @@ fn show_substructure( let args = thin_vec![ fmt, - if is_struct { cx.expr_name(span, sym::names) } else { expr_name }, + if is_struct { cx.expr_name(span, sym::names) } else { expr_name() }, cx.expr_name(span, sym::values) ]; From 3cd9cd090194d76490d805cbad6c8a6ab46c4a4b Mon Sep 17 00:00:00 2001 From: panstromek Date: Mon, 26 Oct 2026 07:30:35 +0100 Subject: [PATCH 14/15] test: bless macro stats test --- tests/ui/stats/macro-stats.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/stats/macro-stats.stderr b/tests/ui/stats/macro-stats.stderr index e4f5a95ac7fad..4806b3ad82c90 100644 --- a/tests/ui/stats/macro-stats.stderr +++ b/tests/ui/stats/macro-stats.stderr @@ -8,8 +8,8 @@ macro-stats q! 1 26 26.0 macro-stats #[derive(Ord)] 1 14 14.0 504 504.0 macro-stats #[derive(Default)] 2 14 7.0 405 202.5 macro-stats #[derive(Eq)] 1 10 10.0 311 311.0 -macro-stats #[derive(Debug)] 1 7 7.0 276 276.0 macro-stats #[derive(PartialEq)] 1 7 7.0 267 267.0 +macro-stats #[derive(Debug)] 1 7 7.0 267 267.0 macro-stats #[derive(PartialOrd)] 1 7 7.0 255 255.0 macro-stats #[derive(Copy)] 1 1 1.0 60 60.0 macro-stats p! 1 3 3.0 32 32.0 From 4719edece749ef683e8971e6e08fd643744a82d0 Mon Sep 17 00:00:00 2001 From: panstromek Date: Sun, 25 Oct 2026 14:08:39 +0100 Subject: [PATCH 15/15] ref: put names before field values in debug helpers --- .../src/deriving/debug.rs | 21 +++++++++---------- library/core/src/fmt/mod.rs | 10 ++++----- tests/ui/derives/deriving-all-codegen.stdout | 20 +++++++++--------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index f74749ac28b0d..f24f3f2df007d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -128,25 +128,24 @@ fn show_substructure( let mut args = ThinVec::with_capacity(2 + fields.len()); args.extend([fmt]); - let mut names = String::new(); if is_struct { - names.push_str(ident.name.as_str()) - } else { - args.push(expr_name()); - }; + let mut names = String::new(); + names.push_str(ident.name.as_str()); - for (i, field) in fields.into_iter().enumerate() { - if is_struct { + for field in fields.iter() { names.push(' '); names.push_str(field.name.unwrap().name.as_str()); } - let field = expr_for_field(field, i); - args.push(field); - } - if is_struct { let name_expr = cx.expr_str(span, Symbol::intern(&names)); args.push(name_expr); + } else { + args.push(expr_name()); + }; + + for (i, field) in fields.into_iter().enumerate() { + let field = expr_for_field(field, i); + args.push(field); } let expr = cx.expr_call_global(span, fn_path_debug, args); BlockOrExpr::new_expr(expr) diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 394d49321d0bf..51ba40e50ae08 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -2496,7 +2496,7 @@ impl<'a> Formatter<'a> { /// faster for 1 field. #[doc(hidden)] #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] - pub fn debug_struct_field1_finish<'b>(&'b mut self, value1: &dyn Debug, names: &str) -> Result { + pub fn debug_struct_field1_finish<'b>(&'b mut self, names: &str, value1: &dyn Debug) -> Result { let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, names.next().unwrap()); builder.field(names.next().unwrap(), value1); @@ -2510,9 +2510,9 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field2_finish<'b>( &'b mut self, + names: &str, value1: &dyn Debug, value2: &dyn Debug, - names: &str, ) -> Result { let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, names.next().unwrap()); @@ -2528,10 +2528,10 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field3_finish<'b>( &'b mut self, + names: &str, value1: &dyn Debug, value2: &dyn Debug, value3: &dyn Debug, - names: &str, ) -> Result { let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, names.next().unwrap()); @@ -2548,11 +2548,11 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field4_finish<'b>( &'b mut self, + names: &str, value1: &dyn Debug, value2: &dyn Debug, value3: &dyn Debug, value4: &dyn Debug, - names: &str, ) -> Result { let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, names.next().unwrap()); @@ -2570,12 +2570,12 @@ impl<'a> Formatter<'a> { #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] pub fn debug_struct_field5_finish<'b>( &'b mut self, + names: &str, value1: &dyn Debug, value2: &dyn Debug, value3: &dyn Debug, value4: &dyn Debug, value5: &dyn Debug, - names: &str, ) -> Result { let mut names = names.split(' '); let mut builder = builders::debug_struct_new(self, names.next().unwrap()); diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 1adf6ff3482a3..3d95302659c4d 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -101,8 +101,8 @@ impl ::core::marker::Copy for Point { } impl ::core::fmt::Debug for Point { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field2_finish(f, &self.x, - &&self.y, "Point x y") + ::core::fmt::Formatter::debug_struct_field2_finish(f, "Point x y", + &self.x, &&self.y) } } #[automatically_derived] @@ -185,8 +185,8 @@ impl ::core::marker::Copy for PackedPoint { } impl ::core::fmt::Debug for PackedPoint { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field2_finish(f, &{ self.x }, - &&{ self.y }, "PackedPoint x y") + ::core::fmt::Formatter::debug_struct_field2_finish(f, + "PackedPoint x y", &{ self.x }, &&{ self.y }) } } #[automatically_derived] @@ -338,8 +338,8 @@ impl ::core::marker::Copy for SingleField { } impl ::core::fmt::Debug for SingleField { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field1_finish(f, &&self.foo, - "SingleField foo") + ::core::fmt::Formatter::debug_struct_field1_finish(f, + "SingleField foo", &&self.foo) } } #[automatically_derived] @@ -749,8 +749,8 @@ impl ::core::fmt::Debug for Generic where T::A: ::core::fmt::Debug { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::debug_struct_field3_finish(f, &self.t, - &self.ta, &&self.u, "Generic t ta u") + ::core::fmt::Formatter::debug_struct_field3_finish(f, + "Generic t ta u", &self.t, &self.ta, &&self.u) } } #[automatically_derived] @@ -1038,7 +1038,7 @@ impl ::core::fmt::Debug for Enum1 { match self { Self::Single { x: __self_0 } => ::core::fmt::Formatter::debug_struct_field1_finish(f, - &__self_0, "Single x"), + "Single x", &__self_0), } } } @@ -1305,7 +1305,7 @@ impl ::core::fmt::Debug for Mixed { &__self_0), Self::S { d1: __self_0, d2: __self_1 } => ::core::fmt::Formatter::debug_struct_field2_finish(f, - __self_0, &__self_1, "S d1 d2"), + "S d1 d2", __self_0, &__self_1), } } }