JelteF/derive_more

`Deref` and `DerefMut` for enums

Opened this issue · 4 comments

Is there a technical reason not to support Deref and DerefMut for enum input types? Seems to me like this would work pretty idiomatically so long as the different variants all supported a consistent target.

For example:

#[derive(derive_more::Deref)]
enum MyEnum1<'a> {
    Variant1(&'a [u8]),
    Variant2(&'a [u8]),
    Variant3(&'a [u8]),
}

#[derive(derive_more::Deref)]
#[deref(forward)]
enum MyEnum2<'a> {
    Variant1([u8; 4]),
    Variant2(Vec<u8>),
}

For the cases you describe I agree it should be possible (at least the first one, the second one might be harder). So no technical reason, just no-one implemented it yet.

+1 for this. My use case could easily be derived:

enum Compression {
    Stored(u32),
    Zlib(u32),
    LZMA1(u32),
}

impl Deref for Compression {
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Stored(size) | Self::Zlib(size) | Self::LZMA1(size) => size,
        }
    }
}

impl DerefMut for Compression {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            Self::Stored(size) | Self::Zlib(size) | Self::LZMA1(size) => size,
        }
    }
}

I had a short session where I tried to implement this feature. I came across a few speed bumps which changed my opinion a little.

1. We can only support enums with "unnamed" data fields

We already know one condition is that the data fields across the input enum must be consistent in some way. Additionally, unless I'm missing something, we can't support the "named fields" enum data type. Even if data were consistent, with named fields it's not clear what the Deref::Target type should be. For example.

enum MyEnum {
  Variant1 {
    named_field1: u8,
    named_field2: bool,
  },
  Variant2 {
    named_field1: u8,
    named_field2: bool,
  },
}

These variants with named field data are not proper types, so there is no clear type to return in these cases. I guess we could annotate a named field in each variant with #[enabled] - and this would be the target type / value. My gut feeling is that this would feel a bit too contrived.

So I guess this means that we can only support enums with unnamed fields as data.

2. We can only support unnamed fields with 1 tuple field

Even if we restrict support to enums with unnamed fields style data, there's still an issue if the data contains more than one field. For example:

enum MyEnum {
  Variant1(u8, bool),
  Variant2(u8, bool),
}

What would the Deref::Target type be here? It's tempting to try using tuple: type Target = (u8, bool) however, again because the data in an enum variant is not a proper type, we don't actually have such tuple values to reference and return in our Deref::deref implementation.

So additionally we would need to restrict support for deriving Deref to just enums with unnnamed fields data with precicely one unnamed field.

Summary

So overall deriving Deref / DerefMut for enums would have to be restricted to only enums with consistent unnamed field data, each with precisely one unnamed field. My gut feeling now is that implementing this is not worth the trouble due to the lack of generality. I'm keen to hear what other people think, though.

@BenLeadbetter I think you're making this more complicated than necessary. We can use the same approach that we use for structs with multiple fields here: Adding a #[deref] attribute to the field that you want to create the deref for. It's shown in the documentation in the first example code block (as the third struct):

// You can specify the field you want to derive `Deref` for.
#[derive(Deref)]
struct CoolVec {
    cool: bool,
    #[deref]
    vec: Vec<i32>,
}

So your examples with enums should then look like this, if you want to to create a Deref derive for u8:

#[derive(Deref)]
enum MyEnum {
  Variant1 {
    #[deref]
    named_field1: u8,
    named_field2: bool,
  },
  Variant2 {
    #[deref]
    named_field1: u8,
    named_field2: bool,
  },
}

enum MyEnum {
  Variant1(#[deref] u8, bool),
  Variant2(#[deref] u8, bool),
}