I'm struggling with the topic name, and even what forum is the best place to put this, if someone can give me a better idea of what to call this and where to put it, I'm all ears!
Problem outline:
I'm creating a new crate with a new trait that I've defined. I want to supply a derive macro for my trait. However, like many common traits, the trait is recursive; e.g., when you derive Clone
, the derive macro produces code assuming that all elements of the type it's operating on are also Clone
, and it assumes this recursively. As soon as it hits an object that hasn't had it derived on it, then you'll get a compile error, and will need to manually implement the trait.
By itself, this isn't a big deal, but because of the orphan rule (1, 2, 3), there are restrictions on crate authors implementing foreign traits on foreign types, so to be a nice crate author, I want to supply implementations of my trait on some common libraries (e.g. core
, std
, the top few most downloaded on crates.io) so that others don't have to do any newtype trickery just to get around the orphan rule.
So how do I do this today? As far as I know, it's a manual process, looking into each crate that you're interested in implementing your trait on, and producing implementations for everything of interest. This is slow and error prone for all of the following reasons:
- Large libraries like
core
andstd
can have a very large number of types defined within them. If you accidentally miss one then you've got a bug in your crate. This is irritating. - Some crates have intricate configuration rules. To see an example, read through the contents of
std::src::sys
. Implementing a trait manually may require you to duplicate that configuration logic so that you can customize the trait implementation as needed. - Some crates produce new types at compile time. For example, if you're using the derive_builder crate, then there will be types that don't exist in what you've personally typed in.
- Possible issues with
build.rs
(I don't really know about this, I don't use it, I'm just aware of it).
Taken together, I end up with the following issues:
- Point 1 means bug reports all day, every day, because of yet another type that you missed.
- Points 1 & 2 mean even more bug reports, probably on systems I don't have direct access to, so I have to rely on my CI setup to catch the bugs, and which means that my CI has to be as complete as possible, and which means that what would normally be a fast turnaround time slows to a crawl.
- Point 3 means that there may be types defined that I'm not even aware of (
cargo-expand
helps with this, but produces a lot of output). - Point 4 may mean that
cargo-expand
is powerless.
My idea
rustc
is already able to figure out what types there are in a crate; if it weren't able to, it wouldn't be able to compile the crate. Is there a way to leverage this ability to produce a new crate from a crate that has same configuration flags, etc., but with just the defined types in place? The newly produced crate could be included in other crates behind a feature flag, reducing its footprint within the compiled binary. E.g., while my crate has trait implementations for serde
, unless you include serde
in your crate you won't have to compile all the trait implementations I have in my crate.
I don't know if it could handle points 3 and 4 above, but if it were able to, then it would pretty solve the headaches the orphan rule produces.