我有一个 struct 如下的枚举:

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

其中每个变体的"成员"都是 struct .

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

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

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

推荐答案

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

不过,我冒昧地重新命名了这个函数,因为enum的组件被称为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));
}

不过,宏确实有局限性——所有变体都需要有一个变体.支持单元变量、多个类型的变量、 struct 变量、可见性等都是real hard.也许一个程序宏会让它更容易一些.

Rust相关问答推荐

值为可变对象的不可变HashMap

有没有办法避免在While循环中多次borrow `*分支`

如何将映射反序列化为具有与键匹配的字段的定制 struct 的向量?

为什么`str`类型可以是任意大小(未知大小),而`string`类型的大小应该是已知的?

如何在Rust中缩短数组

rust中的库插件管理器,现在是否可行?

处理带有panic 的 Err 时,匹配臂具有不兼容的类型

如何重命名 clap_derive 中的子命令占位符?

我可以解构self 参数吗?

如何迭代存储在 struct 中的字符串向量而不移动它们?

sha256 摘要仅适用于 &*

`tokio::pin` 如何改变变量的类型?

Rust typestate 模式:实现多个状态?

如果不满足条件,如何在 Rust 中引发错误

将 `&T` 转换为新类型 `&N`

试图理解 Rust 中的可变闭包

如何在 C++ 和 Rust 之间共享 pthread 同步原语?

如何制作具有关联类型的特征的类型擦除版本?

为什么 u64::trailing_zeros() 在无分支工作时生成分支程序集?

在 macro_rules 中转义 $ 美元符号