Include pre-monomorphised compiled version for items that are generic over object-safe trait objects when building a library crate;
Introduce a way for compiler to introduce dynamic dispatch “under the hood”, without modifying source code from <T> to &dyn.
Introduce a hint like #[prefer_dyn] that tells this generic item should be preferrably used in a dynamic way, even if source code looks generic. There can also be a more aggressive opt_level=s optimization making compiler introducing dyndispatch even not when asked.
Motivation
I saw a comment that compains that there is too much generic and too little dynamic in the ecosystem, leading unavoidably increased compile times and maybe binary sizes.
But explicitly making library APIs based dynamic dispatch instead of generic seems like a stepping down from Rust ideas and revoking user’s capability to use it in a generic way.
Example
Library:
pub trait MyTrait { fn lol(&self); }
struct Q1; struct Q2; struct Q3;
impl MyTrait for Q1, Q2, Q3 {...};
#[please_prefer_dyn_here]
fn my_func<T:MyTrait>(x : T) {
// do something heavyweight, that requires a lof of compilation efforts
// and not worth inlining or duplicating across those used types,
// yet not revoking users of the generic use entirely.
}
Generic version serialized and included in rlib, like usual;
Additionally, a version as if it were fn my_func(x : &MyTrait) compiled and also included in rlib.
Library author can think again and just remove the attribute to easily revert to generic use without the artificial dyndispatch.
Attribute can be added in backward compatible way.
I’m sceptical that it would do a lot to drop compile times.
Items like Vec which are used many times in many ways are going to give quite significant overhead if replaced with a dynamic/run-time-generic version. On the other hand, quite often domain-specific generic code only gets used with a single parametrisation anyway.
Further, with the approach you suggest the compiler still has to create generic parametrisable forms of types (like my_func) when parsing the code because it doesn’t know that only the dynamic version will get used.
Compile times aside (while it could decrease times of client code, it would also increase them for the generic code itself), this would be most beneficial when combined with dynamic linking, to reduce memory footprint. For the initial experiment, it isn’t even necessary to include any syntax/attributes, but rather just add a compiler argument that allows this (on both sides).
As a further argument for not bothering with syntax at the beginning, it would technically count as a simple optimization, and you could implement and test it on all existing code without even needing RFC.
Come to think of it, I’ve written some very large functions that take some <P: AsRef<Path>> or <R: Read> which are entirely finished using said object by the end of the first statement. Maybe we can solve that problem instead.
I.e. use some heuristic to transform this:
pub fn read<R: Read>(r: R) -> Result<Config> {
let raw = ::serde_yaml::from_reader(r)?;
// ...lots of code...
}
into this:
pub fn read<R: Read>(r: R) -> Result<Config> {
// this gets compiled once
fn _impl(a: ::serde_yaml::Result<RawConfig>) -> Result<Config> {
// ...lots of code...
}
// this gets compiled many times
_impl(::serde_yaml::from_reader(r))
}
Sounds like an ideal use case for MIR optimizations, I think.
LLVM can do a more general form of this transformation under the name “outlining,” but that means doing the monomorphization and recognizing the common parts of the function body. MIR would be able to tell right away whether something depends on type parameters.