【问题标题】:Compare enums only by variant, not value仅按变体比较枚举,而不是值
【发布时间】:2015-09-13 20:34:47
【问题描述】:

我有一个具有以下结构的枚举:

enum Expression {
    Add(Add),
    Mul(Mul),
    Var(Var),
    Coeff(Coeff)
}

每个变体的“成员”都是结构。

现在我想比较两个枚举是否具有相同的变体。所以如果我有

let a = Expression::Add({something});
let b = Expression::Add({somethingelse});

cmpvariant(a, b) 应该是true。我可以想象一个简单的双 match 代码,它遍历了两个枚举实例的所有选项。但是,我正在寻找一个更好的解决方案,如果它存在的话。如果没有,双重比赛是否有开销?我想在内部我只是比较两个整数(理想情况下)。

【问题讨论】:

  • 与您的问题无关,但您能告诉我Add(Add) 的语法是什么意思吗?第一个Add 是什么,第二个是什么?
  • @moose:第一个 Add 是枚举变量的名称。第二个是该变体的类型,它假定存在名为Add 的另一种类型(struct 或另一个enum,或者可能是类型别名),其定义未在此处显示。请注意,变体的名称不需要与这些变体的类型名称相同,这正是 OP 选择命名它们的方式。

标签: rust


【解决方案1】:

从 Rust 1.21.0 开始,您可以使用 std::mem::discriminant:

fn variant_eq(a: &Op, b: &Op) -> bool {
    std::mem::discriminant(a) == std::mem::discriminant(b)
}

这很好,因为它可以非常通用:

fn variant_eq<T>(a: &T, b: &T) -> bool {
    std::mem::discriminant(a) == std::mem::discriminant(b)
}

在 Rust 1.21.0 之前,我会匹配两个参数的元组,并忽略带有 _.. 的元组的内容:

struct Add(u8);
struct Sub(u8);

enum Op {
    Add(Add),
    Sub(Sub),
}

fn variant_eq(a: &Op, b: &Op) -> bool {
    match (a, b) {
        (&Op::Add(..), &Op::Add(..)) => true,
        (&Op::Sub(..), &Op::Sub(..)) => true,
        _ => false,
    }
}

fn main() {
    let a = Op::Add(Add(42));

    let b = Op::Add(Add(42));
    let c = Op::Add(Add(21));
    let d = Op::Sub(Sub(42));

    println!("{}", variant_eq(&a, &b));
    println!("{}", variant_eq(&a, &c));
    println!("{}", variant_eq(&a, &d));
}

我冒昧地重命名了函数,因为枚举的组件被称为 variants,实际上您是在测试它们是否相等,而不是比较它们(通常用于排序/排序)。

为了性能,让我们看看 Rust 1.16.0 在发布模式下生成的 LLVM IR。 Rust Playground 可以轻松地向您展示:

define internal fastcc zeroext i1 @_ZN10playground10variant_eq17h3a88b3837dfe66d4E(i8 %.0.0.val, i8 %.0.0.val1) unnamed_addr #0 {
entry-block:
  %switch2 = icmp eq i8 %.0.0.val, 1
  %switch = icmp ne i8 %.0.0.val1, 1
  br i1 %switch2, label %bb5, label %bb4

bb3:                                              ; preds = %bb5, %bb4
  br label %bb6

bb4:                                              ; preds = %entry-block
  br i1 %switch, label %bb6, label %bb3

bb5:                                              ; preds = %entry-block
  br i1 %switch, label %bb3, label %bb6

bb6:                                              ; preds = %bb5, %bb4, %bb3
  %_0.0 = phi i1 [ false, %bb3 ], [ true, %bb4 ], [ true, %bb5 ]
  ret i1 %_0.0
}

简短的版本是我们打开一个枚举变体,然后与另一个枚举变体进行比较。它总体上非常有效,但令我惊讶的是它不只是直接比较变体编号。也许这是优化过程可以解决的问题?

如果你想有一个宏来生成函数,这样的事情可能是一个好的开始。

struct Add(u8);
struct Sub(u8);

macro_rules! foo {
    (enum $name:ident {
        $($vname:ident($inner:ty),)*
    }) => {
        enum $name {
             $($vname($inner),)*
        }

        impl $name {
            fn variant_eq(&self, b: &Self) -> bool {
                match (self, b) {
                    $((&$name::$vname(..), &$name::$vname(..)) => true,)*
                    _ => false,
                }
            }
        }
    }
}

foo! {
    enum Op {
        Add(Add),
        Sub(Sub),
    }
}

fn main() {
    let a = Op::Add(Add(42));

    let b = Op::Add(Add(42));
    let c = Op::Add(Add(21));
    let d = Op::Sub(Sub(42));

    println!("{}", Op::variant_eq(&a, &b));
    println!("{}", Op::variant_eq(&a, &c));
    println!("{}", Op::variant_eq(&a, &d));
}

虽然宏确实有限制 - 所有变体都需要有一个变体。支持单元变体、多于一种类型的变体、结构变体、可见性等都是真正的困难。也许一个程序宏会使它更容易一些。

【讨论】:

  • 谢谢!我将编辑我的原始帖子以反映正确的语言。你知道双重匹配的开销吗?
  • discriminant_value 的存在是为了做到这一点,尽管它是内在的,但它是不稳定的。它至少在为deriving 生成代码时使用,因此至少 ==、
  • 我怎样才能使这个函数比枚举类型更通用?还是可以把它作为一个宏?
  • 还是很丑。我想要像some_variant == SomeEnum::Variant(_) 这样的东西。相反,我必须编写几十个助手。
  • @RecursiveExceptionException 因为这不是 OP 的目标。他们有两个希望相互比较的枚举实例。您的代码解决了“这是一个实例这个特定变体”的问题。 How do I conditionally check if an enum is one variant or another? 涵盖了这一点
猜你喜欢
  • 2023-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-14
相关资源
最近更新 更多