Implementing `Index` for enums to get-variant-or-panic

In short, I want to be able to do let x = X::A(5); x[X::A];, where it panics (like a Vec out of range) if it's not the correct enum variant.

I've tried quite a few different approaches which I won't list here for brevity. Here is a simple example of the kind of thing I'm looking for:

#![feature(unboxed_closures)]
enum X {
	A(u8,u8),
	B(u8)
}
impl<F,T> std::ops::Index<F> for X where F: std::ops::Fn<T, Output=X> {
	type Output = T;
	fn index(&self, x: F) -> &T {
		unimplemented!();
	}
}
fn main() {
	let a = X::A(10,11);
	let b = X::B(12);
	println!("{:?}, {:?}", a[X::A], b[X::B]);
}

This complains because T is unconstrained (since a single type can implement Fn multiple times for different types).

I'm aware that I can do this easily in a number of ways (e.g. a[X::A.some_method()], a[method(X::A)]), but I'm interested in whether this can be done as-is.

1 Like

I waved away the other ways of doing this by just saying they're 'easy', but I've realised this isn't actually possible to do in general.

Given X::A above, Index promises it will return a &T for some T. But there's actually no T that's valid to return a reference to - you can't return a tuple reference pointing to the enum data because that makes assumptions about the enum data layout, and you can't construct any other type and return a reference because it'll die when the stack frame goes away.

So I think the only way to do this is to make every enum variant contain a single tuple so we can talk about fn(T) -> X and return references to the &T.

#![feature(unboxed_closures)]

use std::any::Any;
use std::marker::PhantomData;

#[derive(Debug)]
enum X {
        A((u8,u8)),
        B(u8)
}
#[derive(Debug)]
struct Carry<T> {
        ptr: *const (),
        m: PhantomData<T>,
}
fn imp<T>(f: fn(T) -> X) -> Carry<T> {
        Carry { ptr: f as *const fn(T) -> X as *const (), m: PhantomData }
}

impl<T: 'static> std::ops::Index<Carry<T>> for X {
        type Output = T;
        fn index(&self, x: Carry<T>) -> &T {
                let xp = x.ptr;
                let xa = imp(X::A).ptr;
                let xb = imp(X::B).ptr;
                macro_rules! downcast {
                        ($p:pat, $v:ident) => {{
                                if let $p = *self {
                                        ($v as &Any).downcast_ref::<T>().unwrap()
                                } else { panic!(stringify!($p)) }
                        }};
                }
                match xp {
                        _ if xp == xa => downcast!(X::A(ref v), v),
                        _ if xp == xb => downcast!(X::B(ref v), v),
                        _ => panic!("invalid variant"),
                }
        }
}
fn main() {
        let a = X::A((10,11));
        let b = X::B(12);
        println!("{:?}", a[imp(X::A)]);
        println!("{:?}", b[imp(X::B)]);
}

Here's an example of doing it which a) requires calling a function and b) only works as long as all of your enum variants are single-item tuple variants. I believe you could eliminate this requirement to call a function if https://github.com/rust-lang/rust/issues/40085 was fixed but (as explained above) I don't think it's generally possible to solve this for multi-item tuple/struct enum variants

I fixed the issue which makes fn items not coerce to fn pointers, so the additional function is no longer necessary and makes it much simpler. The following works in the latest nightly:

use std::any::Any;

#[derive(Debug)]
enum X {
    A((u8,u8)),
    B(u8)
}

impl<T: 'static> std::ops::Index<fn(T) -> X> for X {
    type Output = T;
    fn index(&self, x: fn(T) -> X) -> &T {
        fn to_p<T>(f: fn(T) -> X) -> *const () { f as *const fn(T) -> X as *const () }
        let xp = to_p(x);
        let xa = to_p(X::A);
        let xb = to_p(X::B);
        macro_rules! downcast {
            ($v:ident) => { ($v as &Any).downcast_ref::<T>().unwrap() };
        }
        match *self {
            X::A(ref v) if xp == xa => downcast!(v),
            X::B(ref v) if xp == xb => downcast!(v),
            _ => panic!("invalid variant"),
        }
    }
}
fn main() {
    let a = X::A((10,11));
    let b = X::B(12);
    println!("{:?}", a[X::A]);
    println!("{:?}", b[X::B]);
}

There's probably not much more that can be done now without having information about enum internal representations.

2 Likes