Rust's derive often implies inline
yossarian.net
yossarian.net
That being said, one of the Rust devs indicated in the corresponding lobste.rs discussion [2] that they're open to revisiting/rebalancing things if they get enough bug reports indicating something is up, so it might not hurt to tag onto the bug report the author will (hopefully) eventually submit.
[0]: https://github.com/rust-lang/rust/pull/117727
[1]: https://github.com/rust-lang/rust/pull/118031
[2]: https://lobste.rs/s/dldhpw/rust_s_derive_often_implies_inlin...
Something does seem a little off about this, though. Ideally for this `Debug` case there would be an annotation that says "compile this lazily, don't inline". Maybe there doesn't even need to be a new annotation, just `#[inline] #[cold]`. Which looks pretty weird, but might work already.
My idea above is roughly equivalent to the "zero-config" strategy you mention.
It's kinda crazy that if a direct dependency doesn't use some feature from a transitive dependency and the direct dependency forgets to disable it or expose a way to disable it you're still stuck with it.
That being said, even the justifying performance improvement PR was itself a mix of improvements and regressions
Moreover, how would you deal with downstream usage by dependents? Should I be able to make my dependency's type implement Debug (which is at least in spirit a violation of the orphan rule, and would make the bookkeeping/extra logic described above explode for any non-trivial dependency tree)?
People already find the Rust compiler too slow. If there's budget for adding more expensive checks, I don't think I'd want it to be spent on something like this.
I really doubt lazily code genning the Debug trait makes things worse or is super expensive to track. The main tension is more how this interplays with LTO (which I'm guessing would be unable to do this) and designing a generic language feature here for macros.
A rough sketch of the idea would be that you can annotate functions with #[lazy] and the Rust compiler knows to save off the function body for later and only keep the function definition around (no codegen). Then if it does need to codegen because it gets invoked from a non-#[lazy] function, the function is treated as inlineable and #[cold] so that if it's not inlined it's put in a cold region of the binary. It's non-trivial because you somehow need to put the codegen back into the original crate's object file which is probably tricky.
I don't understand the concern regarding the orphan rule or "downstream usage by dependents" - that's not relevant here and no language semantics change.
When you're compiling a library, anything that's exported is a possible entrypoint for the purpose of dead code elimination. When compiling a binary, the only entrypoint is the `main` function. Apart from that, the dead code elimination algorithm is basically the same.
Compare https://rust.godbolt.org/z/osMY8777W and https://rust.godbolt.org/z/azGjbbzc8. I'm not sure why the inlining doesn't happen in the case when we do use the Debug implementation though.
> within spitting distance of any other representation
If marking Debug as #[inline] vs #[noinline] has an effect, it's clearly already true that there's a lot of time being spent on emitting the Debug trait eagerly and having it go through all the compiler stages.
Note that Rust already does dead code elimination at a couple different levels by default, and generic link-time optimization is always an option on top of that, so I assumed you weren't just referring to that.
You can control this behavior with opt level "s" or "z" or "#[inline(never)]", but be aware that too little inlining can have large negative performance impacts.
It's hard to get inlining exactly right without profile guided optimization.
Across all languages with compilers that do it, and even with a mix of PGO data, a data set that brings another dynamic dispatch workflow into action might mess it all, by going into the else part of the optimization.