I came across the same problem which was already asked two other times:
I came up with an Idea of how one might be able to make this work with procedural macros. I already tried implementing first steps but am stuck at one particular point from which I cannot seem to solve the problem.
Let me explain the basic idea:
// This struct has two generic parameters which depend on features
// This code will compile just fine
Struct<
#[cfg(feature = "first") A,
#[cfg(feature = "second")] B,
> {...}
// Hopefully this procedural macro can fix our problem.
// The implementation of [Trait] for [Struct] should be doable with
// any combination of features enabled/disabled.
#[feature_generics(
#[cfg(feature = "first") A,
#[cfg(feature = "second") B
)
impl<A, B> Trait<A, B> for Struct<A, B> {...}
- Create an attribute procedural macro, which will be used on every
impl<T1, T2, ...>
- The attributes of the macro will tell the compiler which generic parameters are desired with which features enabled.
- We parse the
impl
block and replace every occuring instance of<A, B>
with either<A, B>
,<A>
,<B>
or nothing depending on which features are enabled. - The inside of the
impl
block will still be needed to be guarded by the correct#[cfg(feature = ...)]
flags at relevant positions.
The problem with this approach seems that it is not possible to evaluate if a feature is enabled when we parse it as a token from a token stream. The compiler knows about enabled features since #[cfg(feature = ...)]
will work in procedural macros.
There are two options how to fix this problem which I tried with no luck.
- Evaluate if a feature is defined via the
cfg!(feature = ...)
macro. This does not seem to be working as it is not possible to unpack aproc_macro::TokenTree
as a string inside the macro. Rather, the written variable will be parsed (ie.cfg!(feature = token.as_string())
will be looking for the feature"token.as_string()"
) - Find the names of all currently enabled features and see if the
TokenTree
is contained in there. However, I have not found a way to list enabled features, not even with unstable intrinsics of thestd
crate.
The last option would be to generate the code for each possible implementation and guard it with multiple respective #[cfg(feature = ...)]
flags.
#[cfg(feature = "first")]
#[cfg(feature = "second")]
impl<A, B> Trait<A, B> for Struct<A, B> {...}
#[cfg(feature = "first")]
#[cfg(not(feature = "second"))]
impl<A> Trait<A> for Struct<A> {...}
#[cfg(not(feature = "first"))]
#[cfg(feature = "second")]
impl<B> Trait<B> for Struct<B> {...}
#[cfg(not(feature = "first"))]
#[cfg(not(feature = "second"))]
impl Trait for Struct {...}
This last solution was not tried by me so far and seems not very nice since it leads to loads of code being generated before being thrown out again. This intermediate step scales by 2^N_GENERICS
.