2015-09-13 11 views
5

を重視、唯一のバリアントで列挙型の比較:各バリアントの「メンバー」は、構造体ですない私は、以下の構造を持つ列挙型を持っている

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

ここでは、2つの列挙型が同じバリアントを持っているかどうかを比較したいと思います。私は

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

cmpvariant(a, b)を持っているのであればtrueでなければなりません。私は両方の列挙型インスタンスのすべてのオプションを通過する単純なdouble matchコードを想像することができます。しかし、私はそれが存在する場合は、見事な解決策を探しています。もしそうでなければ、ダブルマッチのオーバヘッドはありますか?私は内部的には2つのint(理想的には)を比較していると思います。

+0

質問には関係ありませんが、「追加(追加)」という構文の意味を教えてください。最初の「追加」とは何ですか?また、2番目のものは何ですか? –

+2

@mose:最初の 'Add'は列挙型の名前です。 2番目はそのバリアントの型であり、 'Add'という名前の別の型(' struct'や別の 'enum'、おそらくは型別名)の存在を前提としています。バリアントの名前は、バリアントのタイプの名前と同じである必要はないことに注意してください。これは、OPが名前を付けた方法です。 –

答えて

11

錆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) 
} 

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

通常のソート/注文のために使用されている(列挙型のコンポーネントはバリアントと呼ばれているように私は、しかし機能の名前を変更する自由を取って、本当にあなたは、それらが等しいかどうかを確認するためにテストしている、それらを比較していません)。

パフォーマンスについては、リリースモードでRust 1.16.0によって生成されたLLVM IR inを見てみましょう。 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 
} 

をショートバージョンが、その後、他の列挙型変異体と比較し、我々は1つの列挙型のバリアントのスイッチを行うことです。それは全体的にかなり効率的ですが、私は変種数を直接比較するだけではないことに驚いています。おそらく、これは最適化パスが処理できるものでしょうか?

マクロを作成して関数を生成したい場合は、このようなものが良いスタートになるかもしれません。

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

マクロには制限がありますが、すべてのバリアントには1つのバリアントが必要です。サポートするバリアント、複数のバリアント、構造バリアント、可視性などはすべて本物のハードです。おそらく手続き型マクロはそれを少し楽にするでしょう。

+0

ありがとう!私は正しい言語を反映するためにオリジナルの投稿を編集します。あなたはダブルマッチングのオーバーヘッドについて考えていますか? –

+1

本質的であるが、[discriminant_value](http://doc.rust-lang.org/std/intrinsics/fn.discriminant_value.html)はこれを行うために存在し、不安定である。それは少なくとも "導出する"ためのコードを生成するときに使用され、少なくとも==、<その他の演算子は自動的に導出されたときと同じ速さである。 – Mar

+0

この関数を列挙型に対して汎用にするにはどうすればよいですか?それともマクロとして置くことができますか? – shakram02

関連する問題