In this post, I propose to instantiate all templated functions with dynamic dispatch, and rely on inlining for case-by-case optimization. Thanks to Rust’s typesystem, we don’t even need to type-check multiple times.
The benefits are: awesome binary/code size, and fast compile time like C/Go programs. The drawbacks are: they are harder (slower) to inline, and overall it would have less optimized code compared to directly instantiated one. Plus that we will get a complicated ABI.
First, for treating functions:
- If the argument is a (combination of) trait: pass a
&movepointer, like unsized rvalues. Maybe we will have to use multiple vtables (new ABI).
- If the argument is a generic sturct: in addition to pass the struct just as before, pass vtables for the generic arguments.
- If the return type may vary in size, create a companion function that calculates the size and alignment. If it’s a trait method, include this function inside the vtable. The caller allocates the space for return value with alloca.
Second, for treating generic structs:
- We create a function that computes size and alignment inside the vtable for each member which can have varying size.
- Alternatively, a function that computes offset for each member that can have varying offset.
In both cases, there would probably be complex runtime code to compute layout which is not good for inlining. Additionally, it’s harder to preserve things like null-pointer optimization.
Above is my brief idea to achieve polymorphic codegen in all cases. Would this be worthwhile to implement? How does this compares to just boxing everything? Alternatively, should we make this a type system property (like enhanced unsized rvalues) instead?