I have an enum like the following:
enum E<'a> {
#[cfg(feature = "baz")]
Foo(&'a str),
Bar,
}
impl E<'_> { /* stuff */ }
There is a problem: with feature baz
disabled, the definition of E
has an unused lifetime parameter, which means it won't compile. I could manage defining E
twice, once with #[cfg(feature = "baz")]
and with the lifetime (and both variants), and once with #[cfg(not(feature = "baz"))]
and without the lifetime (and just Bar
). But the bigger issue is all of the implementations of E
— they all need to be in sync with the definition as well, and I really don't want to have to write each of them twice.
Ideally I could write something like
impl E<#[cfg(feature = "baz")] '_> { /* stuff */ }
to conditionally include the lifetime parameter, but this obviously doesn't work. Is there a clean solution to this problem? I'd like to not add a PhantomData<&'a ()>
or similar to Bar
just to get this to compile.
I've met this problem myself, and I believe there is no direct solution. You are limited to options which mention the lifetime somehow in the definition of the type. I solved it in my case by using a String
instead of &str
, but if that is undesirable, you will have to add a placeholder variant that uses the lifetime parameter. You can make this variant unusable and hidden, however:
// this enum has zero values (it is “uninhabited”) and so
// anything which contains it cannot exist either
#[doc(hidden)]
pub enum Impossible {}
#[non_exhaustive]
pub enum E<'a> {
#[cfg(feature = "baz")]
Foo(&'a str),
#[cfg(not(feature = "baz"))]
#[doc(hidden)]
_PlaceholderBecauseYouCannotUseFooWhenBazFeatureIsDisabled(Impossible, PhantomData<&'a str>),
Bar,
}
Because of the field of type Impossible
, Rust recognizes that this enum variant cannot actually be constructed; for example, this compiles even though it disregards the placeholder:
fn no_spurious_arms(e: E<'_>) {
match e {
#[cfg(feature = "baz")]
E::Foo(_) => {}
E::Bar => {}
}
}
Note that whenever you make the variants of an enum in a library conditional on features, you must make the enum #[non_exhaustive]
, prohibiting exhaustive matching on it. If this is not done, dependents of your library not using the feature may be broken by another library in the same dependency graph enabling the feature.
If your use case needs exhaustiveness — or if you want to avoid #[doc(hidden)]
shenanigans — then you must keep the enum the same, but what you could do is change the details of the field of the Foo
variant:
use std::marker::PhantomData;
pub enum E<'a> {
Foo(Foo<'a>),
Bar,
}
pub struct Foo<'a> {
#[cfg(feature = "baz")]
pub string: &'a str,
_phantom: PhantomData<&'a str>,
}
Since structs can have private fields, introducing a Foo
struct allows having the needed PhantomData
without exposing it, and adding the conditional field without raising any exhaustiveness issues. The cost of this option is that users have to work with the Foo
struct.