Repository navigation
Derived Debug implementations always include #[inline], which can cause code bloat聽#163955
Description
Hi folks! @RalfJung encouraged me to submit this. I think it's a tricky one, since there's no perfect user-agnostic tradeoff between compile times, binary sizes, and runtime performance 馃檪
For context, this stems from me investigating uv's binary size and ways to reduce it. One of the things I quickly noticed was that ~160KB of our .text in typical releases came from a single derive(Debug) on one of our error types. This didn't make any sense to me (that's a ton of instructions!) until I realized that it was being inlined on every callsite, so effectively we were getting a multiplier for the already relatively large error type's Debug repr.
When I dug into this, I realized that it's because derive(Debug) also introduces #[inline] on the macro-generated fmt. This can be seen with trivial implementations, e.g.:
#[derive(Debug)]
struct Widgets {
foo: u32,
bar: usize,
}becomes:
struct Widgets {
foo: u32,
bar: usize,
}
#[automatically_derived]
impl ::core::fmt::Debug for Widgets {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "Widgets",
"foo", &self.foo, "bar", &&self.bar)
}
}I think I expected there to be some kind of heuristic here around large/deeply nested Debug implementations, i.e. not introduce #[inline] beyond a certain size (or perhaps a certain number of callsites?). I'm not sure if this heuristic would generalize well, however.
Instead, we saw #[inline] regardless of the chunkiness of our Debug impl, and naive inlining of the resulting chunkiness everywhere we did "{err:?}" in debug/trace logging 馃檪
The example above is completely contrived; you can see the actual (much larger) Error type here:
That also shows how we "fixed" it in astral-sh/uv#22141, i.e. by adding our own DebugNoInline proc-macro that behaves like Debug but without any #[inline].
Meta
We've observed this on the last several stable versions, it also reproduces on nightly (using the playground's nightly + macro expansion features).
Other context
I wrote this up here as well, but in a much more "TIL, novice" friendly format that I suspect won't be immediately useful to Rust's maintainers 馃檪
Activity
RalfJung commentedon Oct 7, 2026
The
inlinewas added in #117727. Cc @saethlinasquared31415 commentedon Oct 7, 2026
#118031 (comment) is also relevant, it tries to avoid
#[inline]based on a heuristic, and ended up regressing things significantly.maxdexh commentedon Oct 7, 2026
this would be another interesting use case for allowing derive macros to take args, like
derive(Debug(const, inline(never)))saethlin commentedon Oct 7, 2026
This is a good use-case for LocalCopy codegen without the inlining hint 馃憤
baltazar-arm commentedon Oct 8, 2026
Related: #162544