Yes, and I’d love to hear whether the community thinks this problem is worth tackling. For now I am assuming it is.
My prototype is not entirely implementable with plugins, sadly. It still required a change of the language to allow foreign functions to be generic.
The other part is a post-trans plugin that enumerates all generated monomorphizations of foreign generics and produces the corresponding C++ instantiations for them.
Unfortunately, that is not enough. Even if you embed the C++ compiler inself into rustc, in order to generate additional instantiations, you’ll need [at least the the headers of] the original C++ library. I am afraid the latter requirement is not a reasonable one.
My current thinking is that the following compromise might be acceptable:
- The “wrapper” crate (i.e. the original Rust library wrapping a C++
library) should be able to pre-generate a number of C++
template instantiations as the author sees fit.
- Downstream crates can use this crate without incurring any extra dependencies as long as they stay within this pre-generated subset of types. Preferably, this should be enforced by the type system, for example via trait implementations for the allowed combinations of types (as I tried to illustrate here).
- Downstream crates may request new instantiations to be generated, but in this case they opt in to being dependent on the plugin, the C++ compiler and the C++ library.
Example: let’s say you set out to create a wrapper around std::map;
So you go on to define a generic Rust wrapper type, CppMap<K,V>, and use the cpp! macro in implementations of its methods to call out to std::map.
You also specify that the underlying template should be pre-instantiated for (K,V) in (i32,i32), (String, String), (i32, String) … and so on.
Downstream crates may freely use CppMap with those combinations of type parameters. To make sure of that, there is a trait CppMapParams, which is implemented only for those type combinations, and CppMap is constrained on implementations of this trait.
Later, somebody else creates a new type Foo in their crate, and really would like to use it as a key in CppMap. So they reference the “cpp” plugin from their crate, then write something like cpp_impl!( CppMap for (Foo,i32), (Foo, String), ...), which kicks off generation of the additional std::map instantiations. It also implements CppMapParams for (Foo,i32), (Foo,String), etc.