Search code examples
rustenumsslicetraits

How can Index be implemented on an enum with variants of different types?


I'm trying to implement Index on my enum so that I can take slices of it (an enum of the same variant with a sliced inner value), following Implementing slice for custom type. But because my enum has variants with different types I can't get it to work:

pub enum GlkArray<'a> {
    U8(&'a mut [u8]),
    U32(&'a mut [u32]),
}

impl<'a, Idx> Index<Idx> for GlkArray<'a>
where Idx: SliceIndex<[usize]>
{
    type Output = GlkArray<'a>;
    fn index(&self, index: Idx) -> &Self::Output {
        match self {
            GlkArray::U8(buf) => &GlkArray::U8(&mut buf[index]),
            GlkArray::U32(buf) => &GlkArray::U32(&mut buf[index]),
        }
    }
}

This results in these errors:

error[E0277]: the type `[u8]` cannot be indexed by `Idx`
   --> remglk/src/glkapi/arrays.rs:117:53
    |
117 |             GlkArray::U8(buf) => &GlkArray::U8(&buf[index]),
    |                                                     ^^^^^ slice indices are of type `usize` or ranges of `usize`
    |
    = note: required for `[u8]` to implement `Index<Idx>`
help: consider further restricting this bound
    |
112 | where Idx: SliceIndex<[usize]> + std::slice::SliceIndex<[u8]>
    |                                ++++++++++++++++++++++++++++++

error[E0277]: the type `[u32]` cannot be indexed by `Idx`
   --> remglk/src/glkapi/arrays.rs:118:55
    |
118 |             GlkArray::U32(buf) => &GlkArray::U32(&buf[index]),
    |                                                       ^^^^^ slice indices are of type `usize` or ranges of `usize`
    |
    = note: required for `[u32]` to implement `Index<Idx>`
help: consider further restricting this bound
    |
112 | where Idx: SliceIndex<[usize]> + std::slice::SliceIndex<[u32]>
    |                                +++++++++++++++++++++++++++++++

I kind of understand that SliceIndex wants to be defined over a concrete type, but I'm not sure what to do in this situation. Yes you don't know what variant the enum has from outside, but it's returning another encapsulated enum, so I'm hoping that's okay.

Any ideas how Idx needs to be defined, or any alternative suggestions of what I could do instead?


Solution

  • Unfortunately, you can't implement Index for this type since it would require producing new &mut references from behind a & reference, which is impossible.

    Even when Index can't be implemented correctly, it's still possible that IndexMut could be implemented by making a panicking Index implementation, but IndexMut is also impossible because of lifetime and ownership issues. When you mutably reborrow a slice, it must produce a slice with a new lifetime:

    let mut x = [1, 2, 3];
    // We'll say this reference has lifetime 'a
    let slice = &mut x[..];
    // This one has a new lifetime that's distinct from 'a
    let slice2 = &mut slice[1..];
    

    This means you can't return GlkArray<'a> from IndexMut::index_mut. And the trait is written so that it is impossible to specify a working lifetime for GlkArray.

    In addition, you can't return references to temporary values. Which means returning &mut GlkArray will not work, since you must create a new GlkArray inside the function. This also applies to Index.

    There is one way around both of these, which is to modify the original GlkArray and return a reference to that. But this probably wouldn't be acceptable as a side effect of indexing.

    Instead, you can write your own indexing function. You won't be able to use the indexing operator [_] but it will otherwise work.

    impl GlkArray<'_> {
        // The return of this function cannot be Self
        pub fn get_mut<'a, I>(&'a mut self, index: I) -> Option<GlkArray<'a>>
        where
            I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[u32], Output = [u32]>,
        {
            match self {
                Self::U8(slice) => slice.get_mut(index).map(GlkArray::U8),
                Self::U32(slice) => slice.get_mut(index).map(GlkArray::U32),
            }
        }
    }