enumsrustcastingcode-generationreusability

Cast a Rust enum to a sub-enum


I'm creating subsets of std::sync::atomic::Ordering:

use std::sync::atomic::Ordering;

pub enum StoreOrdering {
    Relaxed,
    Release,
    SeqCst
}
impl Into<Ordering> for StoreOrdering {
    fn into(self) -> Ordering {
        match self {
            Self::Relaxed => Ordering::Relaxed,
            Self::Release => Ordering::Release,
            Self::SeqCst  => Ordering::SeqCst
        }
    }
}
impl std::convert::TryFrom<Ordering> for StoreOrdering {
    type Error = (); // HACK
    fn try_from(ord: Ordering) -> Result<Self, Self::Error> {
        match ord {
            Ordering::Relaxed => Ok(Self::Relaxed),
            Ordering::Release => Ok(Self::Release),
            Ordering::SeqCst  => Ok(Self::SeqCst),
            _ => Err(())
        }
    }
}

enum LoadOrdering {
    Acquire,
    Relaxed,
    SeqCst
}
// ???

As you can see, now I need to write those two impls with matches again for StoreOrdering <-> LoadOrdering and maybe even for StoreOrdering <-> LoadOrdering - as well as for any enum subset. How to avoid such boilerplate?


Solution

  • Rust doesn't support duck typing like C++ does with templates. The only functionality that generics can access is determined by the trait bounds.

    So any duck-type-like behaviour must be done with macros.

    For this, you could use the given macro below. It only works for simple C-style macros. It creates the enum and auto-generates the conversions to the given super-enum.

    use std::sync::atomic::Ordering;
    use std::convert::TryInto;
    
    // Create the store ordering
    sub_enum!(StoreOrdering of Ordering {
        Relaxed,
        Release,
        SeqCst
    });
    
    // Create the load ordering
    sub_enum!(LoadOrdering of Ordering {
        Acquire,
        Relaxed,
        SeqCst
    });
    
    #[macro_export]
    macro_rules! sub_enum {
        ($sub_enum_name:ident of $super_enum_name:ty {
            $($variant:ident),* $(,)?
        }) => {
            pub enum $sub_enum_name {
                $($variant,)*
            }
            
            impl From<$sub_enum_name> for $super_enum_name {
                fn from(val: $sub_enum_name) -> $super_enum_name {
                    match val {
                        $(<$sub_enum_name>::$variant => <$super_enum_name>::$variant,)*
                    }
                }
            }
            
            impl std::convert::TryFrom<$super_enum_name> for $sub_enum_name {
                type Error = ();
                fn try_from(val: $super_enum_name) -> Result<Self, Self::Error> {
                    match val {
                        $(<$super_enum_name>::$variant => Ok(Self::$variant),)*
                        _ => Err(())
                    }
                }
            }
        }
    }
    
    fn main() {
        let store = StoreOrdering::SeqCst;
        let general: Ordering = store.into();
        let load: LoadOrdering = general.try_into().unwrap();
    }
    

    Playground link

    A lot could be improved still, of course. However, this should do for your problem right now.