# Opaque dependencies Cargo's dependencies todays are transparent. You see and control the entire dependency tree and how they are built. This causes limitations when evaluating build-time performance improvements including - Caching: differences in dependency versions cause a unique instance of everything built above it - Pre-built binaries: similar to the above, there are too many combinations to account for. You could fake it and say "pick one and deal with it" but that runs counter to the design of these dependencies - MIR-only or cross-crate lazy building: Every one of your dependencies will be too unique to reuse between binaries within your workspace or cached between projects. Similarly, Every one of the binaries within your workspace will have to do codegen for your dependencies. If we could have a hybrid model where small, lower down dependencies are built lazily but certain heavy-weight dependencies get built and code-genned once, it might strike the right balance. - Optimizing the build for dependencies you are unlikely to debug into. In reflecting on this, this feels similar to the divide between C++ headers-only libraries and dynamic libraries. As a consumer, you don't care how the sausage is made for a dynamic library. You don't care what version of an inner dependency is used. You can swap in or out a debug build, a release build, change various compiler flags, etc and it doesn't affect you. What if we had an opaque dependency with: - Its own `Cargo.lock` - Its own `RUSTFLAGS` - Its own `profile` - Can more easily be switched between rlib and rdylib (reduce link times) Examples of what this might apply to include: - `std` - bevy - gitoxide - tauri - iced - axum `std`? Yes, part of the inspiration for this is the `build-std` effort where you can choose to rebuild `std`. - `std` and `build-std` have their own `Cargo.lock` - At least `std` has its own RUSTFLAGS, `profiles`, etc ## Opportunistic reuse If a dependency happens to be shareable between the an opaque dependency and the caller, should it? - With the same `-Cmetadata`, you could accidentally be using a copy of your dependency where you should be using a re-export from the opaque depencency which could break you on upgrade - Depending on how we handle the MIR-only part of this, there might be fewer opportunities for reuse - Cargo's existing work for reusing between host/target is complicated and doesn't get as much reuse as one would like - However, depending on the caching setup, we might get this "for free" if the `-Cmetadata` can be the same **Open question:** - Should we include the identity of the opaque dependency in all `-Cmetafata` hashes for members of it to avoid accidental API breakages? ## Library packs Something that `std` calls attention to that could be easily overlooked in this initial plan is you can choose to depend on `core`, `alloc`, `std`, `proc_macro`, etc which all come from `sysroot`. TODO how are these linked? Are they separate rlibs but built as a single set? If so, then we may need "library packs" to allow more than one top-level package. This might also work well for loosening of the orphan rules. ## Singletons How do we deal with singletons? - `log` - `colorchoice` - `rayon-core` ## Prior art What lessons can we learn from C++ application development? - On how to configure this and tie it together - In what situatiouns may someone want a static lib vs a dynamic lib? - If profiles are decoupled, how we decide what provide to build within an opaque dependency? - How do we know when to switch profiles of an opaque dependency?