Is it possible to get the enum field directly according to the generic type

pub enum Test {
    A(i32),
    B(u32),
    C(f32),
}

impl Test {
    pub fn get<T>(&self) ->Option<&T> {
        todo!()
    }
}

fn main() {
    let a = Test::A(0);
    let b = a.get::<i32>(); 
}

(Playground)

As the above code shows, how to implement the get function?

For 'static types you could use TypeId:

use std::any::TypeId;

pub enum Test {
    A(i32),
    B(u32),
    C(f32),
}

impl Test {
    pub fn get<T: 'static>(&self) -> Option<&T> {
        // SAFETY: Using `TypeId` we know what type `T` is and whether we store
        //   a value of it in one of our variants. Therefore the reborrows of 
        //   the casted pointers we use below to tell the compiler that we in 
        //   fact return a `T` can be considered safe.
        match (TypeId::of::<T>(), self) {
            (id, Self::A(x)) if id == TypeId::of::<i32>() => {
                Some(unsafe { &*(x as *const i32 as *const T) })
            }
            (id, Self::B(x)) if id == TypeId::of::<u32>() => {
                Some(unsafe { &*(x as *const u32 as *const T) })
            }
            (id, Self::C(x)) if id == TypeId::of::<f32>() => {
                Some(unsafe { &*(x as *const f32 as *const T) })
            }
            _ => None,
        }
    }
}

fn main() {
    let a = Test::A(0);

    let x = a.get::<i32>();
    assert_eq!(x, Some(&0));

    let y = a.get::<u32>();
    assert_eq!(y, None);

    let z = a.get::<f32>();
    assert_eq!(z, None);
}

Playground.

4 Likes

if you are seeking for C++ std::variant and std::get like APIs, you might find this crate interesting:

1 Like

There's no need for unsafe; trait Any provides downcast_ref to encapsulate the pattern.

    pub fn get<T: 'static>(&self) -> Option<&T> {
        let x: &dyn Any = match self {
            Self::A(x) => x,
            Self::B(x) => x,
            Self::C(x) => x,
        };
        x.downcast_ref()
    }
6 Likes

Of course you can also do such "overloading" by defining a trait:

// TODO: apply sealed trait pattern if desired
pub trait VariantTypeOfTest {
    fn get_variant_in_test(test: &Test) -> Option<&Self>;
}

impl Test {
    pub fn get<T: VariantTypeOfTest>(&self) -> Option<&T> {
        T::get_variant_in_test(self)
    }
}

impl VariantTypeOfTest for i32 { … }
impl VariantTypeOfTest for u32 { … }
impl VariantTypeOfTest for f32 { … }

(playground)

4 Likes

Looks like the compiler is not able to optimize this compared to trait dispatch:

That way you always need to carry the T: VariantTypeOfTest bounds everywhere you use it, while the other implementations return None if it isn't a variant. The only way i could think of to get around this is with specialisation and providing a default None return for every T