Don't rebuild dependents when certain criteria is met

this is inspired by this blog post (specifically the "Recompiling dependents on implementation changes" section), as well as being annoyed at how long it takes clippy to recompile whenever the implementation of a single lint is changed.

as far as i can tell, the reason rust requires recompiling dependents instead of just relinking them like in C is because of public generics and cross-crate inlining.

however, if a specific crate simply doesn't use these features, as far as i can tell, there's no need to recompile its dependents for an internal change.

specifically, a dependent D of a modified crate C should skip recompilation if all of the following are met:

  1. C's public API did not change
  2. C's public API does not contain any generic type paramaters
  3. the opt-level for C and D is zero
  4. D does not have a build script

this would make debug builds much faster for certain multipackage projects, such as clippy.

C and C++ require recompiling if any headers they include have changed. Rust doesn't have that header distinction.

You could possibly generate rlibs with non-inlined non-generics split out from the generics and inlined functions, and not have to recompile if only the former changes and the function signatures don't change (possibly other conditions needed too, haven't thought too deeply about it). Would be useful for debugging.

It seems quite complicated and I'm not sure it is where the effort and complexity budget of Rustc is best spent.

How would one know to relink if C uses something like inventory to provide things to be combined together at link time? I see the blog does mention relinking, so that could be moot.

What are you counting as "public API"? Not all things that appear publicly are explicitly pub. Besides the linker shenanigans of inventory (which relinking should cover), we have things like size_of types that have private members. Changing those may change alignment or field packing of the public parameters requiring new codegen to access those members. As long as that counts as "public API", then that's fine. I think a better term would be "public ABI" since that incorporates things like struct layout and such which is not part of the literal API (in that Rust can query them, but cannot in-language control them).

That's all I can think of at this moment, but there's probably other arcana that can be affected without changing a pub item.

And before it is mentioned, modules will likely have this as well. There is some work in Clang to try and distill a "minimal BMI" to try and reduce recompilation pressure, but it is muddled by C++ powers such as SFINAE and ADL that can ask things like "does something matching signature S exist?" meaning that even adding a symbol can cause any given code to change and need recompilation (Rust doesn't yet have such things, but I remember there being (pre-?)RFC thread(s) about some kind of "does symbol exist?" queries for API compatibility purposes). For Clang, I don't understand the internals enough to answer anything in detail as I'm more on the build system side than the compiler internals side; I mostly think about edge cases that could cause problems for these kinds of things on the build side with inconsistencies and such.

I'm not sure how inventory causes problems with this, as its magic happens at link time and runtime, both of which would be completely unchanged by this proposal. only parsing thorugh codegen would be skipped.

everything contained in a minimal (ie. no MIR) rmeta file for the crate. in a perfect world we would simply have a function that detects if two compilations of a crate are abi-compatible, but checking rmeta is pretty close.

hmm, it seems rmeta doesn't work how i thought, but it should still be possible to gather together function signatures and struct/enum layouts.

stable MIR would probably allow this

actually, after messing around with MIR queries a bit, it should be possible to collect some sort of AbiSignature by collecting the type (Ty) of every public item, serializing them, and hashing that.