将Rust枚举转换为子枚举

4
我正在创建 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
}
// ???

如您所见,现在我需要再次使用match来编写那两个impl,分别对应于StoreOrdering <-> LoadOrderingLoadOrdering <-> StoreOrdering,甚至是任何枚举子集。如何避免这种样板代码?

1
Rust不支持鸭子类型(除了宏之类的有点像)。也许有更好的解决方案,那么你想用这些枚举解决什么问题呢? - Geoxion
3
如果你对宏有兴趣,我在这个游乐场里制作了一个:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c646363dae534eb07101fbffe9e7e2b4 - Geoxion
@Geoxion 我正在基于std AtomicPtr编写AtomicRefAtomicPtr :: load()AtomicPtr :: store()接收Ordering,如果不是预期的Ordering子集,则会出现恐慌 - 我觉得这很愚蠢,并希望通过编译时检查(对于我的AtomicRef)来提高质量,因此...就有了这个问题。你的宏似乎很有趣,我应该试试。 - passing_through
1
过程宏对于此问题而言有些过度设计了 - 如果你想传递属性/文档,你可以编写声明性宏,并避免在编译时编译和运行另一个 crate。 - apetranzilla
3
@Geoxion,我建议你将这个发表为一个答案。 - Sven Marnach
显示剩余3条评论
2个回答

5

Rust不支持类似C++的模板那样的鸭子类型(duck typing)。泛型所能访问的唯一功能由特质边界(trait bounds)决定。

因此,任何类似于鸭子类型的行为都必须使用宏来实现。

为此,您可以使用下面提供的宏。它仅适用于简单的C风格宏。它创建枚举并自动生成到给定超级枚举的转换。

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();
}

演示链接

当然,还有很多需要改进的地方。 但是,目前这应该可以解决你的问题。


0

有一个叫做subenum的crate,它有一个派生宏,用于自动创建SubEnum和转换From<SubEnum> for SuperEnum以及TryFrom<SuperEnum> for SubEnum

这是来自crate的readme的一个例子:

use subenum::subenum;

#[subenum(Edible)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Plant {
    #[subenum(Edible)]
    Basil,
    #[subenum(Edible)]
    Tomato,
    Manzanita,
    Pine,
}

fn main() -> Result<(), EdibleConvertError> {
    let plant = Plant::Tomato;

    // We can convert between them.
    let edible = Edible::try_from(plant)?;
    let _plant2 = Plant::from(edible);

    // We can compare them.
    assert_eq!(plant, edible);

    // We derive anything that's derived on the parent, such as clone.
    let edible2 = edible.clone();

    Ok(())
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接