I'm currently trying to implement a protocol library with many parts of the protocol behind feature flags. For that purpose, I was wondering if there is an ergonomic way in Rust to have one of my enum have a lifetime parameter or not, depending on some feature flags.
On the parsing side I have a parsed item called Action
that looks like this:
enum Action<'a> {
Nop(Nop),
WriteFileData(WriteFileData<'a>),
...
}
struct Nop {
pub group: bool,
pub response: bool,
}
struct WriteFileData<'a> {
...
pub data: &'[u8], // Not exactly the final code, but equivalent
}
Up to this point everything is fine.
But then in my library I want each of those action types to be behind a feature flag:
enum Action<'a> {
#[cfg(feature = "decode_nop")]
Nop(Nop),
#[cfg(feature = "decode_write_file_data")]
WriteFileData(WriteFileData<'a>),
...
}
At this point, if you choose only features that decode action types without lifetime (only decode_nop for example), you end up compiling:
enum Action<'a> {
Nop(Nop),
}
Which does not compile because, there's a useless lifetime in the enum declaration.
I first found a promising lead for conditional lifetimes:
enum Action<#[cfg(feature = "decode_action_lifetime")] 'a> {
...
}
impl<#[cfg(feature = "decode_action_lifetime")] 'a> Action<#[cfg(feature = "decode_action_lifetime")] 'a> {
...
}
Unfortunately, this won't compile because:
error: expected one of `>`, const, identifier, lifetime, or type, found `#`
--> src/v1_2/action/mod.rs:123:63
|
123 | impl<#[cfg(feature = "decode_action_lifetime")] 'a> Action<#[cfg(feature = "decode_action_lifetime")]'a> {
| ^ expected one of `>`, const, identifier, lifetime, or type
As this is a syntax error, I'm not even sure that what I tried to do here is legal in Rust.
I then came up with 2 ways of fixing my problem:
Duplicate the Action definition with a lifetime and a non lifetime variant behind the appropriate feature flags combinations.
#[cfg(feature = "decode_with_lifetime")]
enum Action<'a> {
#[cfg(feature = "decode_nop")]
Nop(Nop),
#[cfg(feature = "decode_write_file_data")]
WriteFileData(WriteFileData<'a>),
...
}
#[cfg(not(feature = "decode_with_lifetime"))]
enum Action {
#[cfg(feature = "decode_nop")]
Nop(Nop),
...
}
The problem is that it then requires duplicating all the code that refers to that type:
- impl blocks.
- other functions using this type.
- external code that would use this library while trying to support the same feature flags.
So I'm clearly not a fan of that solution because of its high maintenance cost for my crate and potentially for code using that crate.
Add a lifetime parameter to all sub items without one (Nop<'a>
) using a PhantomData<&'a ()>
marker in their struct.
struct Nop<'a> {
pub group: bool,
pub response: bool,
pub phantom: PhantomData<&'a ()>,
}
But then the public declaration of those non lifetime requiring struct, which I considered part of my API (all fields accessible so that one could build them directly), now suddenly includes an inconvenient and unintuitive phantom field.
So if I want my API to be clearer for a user, it probably means I have to add builder functions for each of those structures so that the user does not have to care about my library implementation details (phantom field).
Plus, Rust does not support any kind of named parameter in its functions (as far as I know) which makes a builder for a struct with lots of fields (> 4 fields) hard to read and error prone to use. The solution would probably be to create a parameter type that is exactly like the first struct without the phantom parameter, but that would mean adding even more code.
struct Nop<'a> {
pub group: bool,
pub response: bool,
pub phantom: PhantomData<&'a ()>,
}
struct NopBuilderParam {
pub group: bool,
pub response: bool,
}
impl<'a> Nop<'a> {
pub new(group: bool, response: bool) -> Self {
Self {
group,
response,
phantom: phantomData,
}
}
}
And, while it should not really be a problem, it bothers me a bit to add this lifetime constraint to a fully owned struct (which implies additional constraints on the user code). But I guess I can live with that one.
In conclusion:
I am currently going with solution number 2. But I would very much like to know if there is another way to do what I'm trying to do.
question from:
https://stackoverflow.com/questions/65652212/how-to-add-a-lifetime-to-an-enum-depending-on-a-compilation-feature-flag