2014-09-30 48 views
18

我有一些特徵是由某些結構實現的。我想寫一個模式匹配,我可以處理每一種可能的情況:如何匹配性狀實現者

trait Base {} 

struct Foo { x: uint } 
struct Bar { y: uint } 

impl Base for Foo {} 
impl Base for Bar {} 

fn test(v: bool) -> Box<Base + 'static> { 
    // Let's pretend there's real logic that determines what to return. 
    if v { 
     box Foo { x: 5 } 
    } else { 
     box Bar { y: 10 } 
    } 
} 

fn main() { 
    let f: Box<Base> = test(true); 

    // Now that we have a `Box<Base>` (`*f` makes it a `Base`), 
    // let's handle different cases: 
    match *f { 
     Foo { x } => println!("it was Foo: {}!", x), 
     Bar { y } => println!("it was Bar: {}!", y), 
    } 
} 

(在線試用:http://is.gd/YuBkPF

我得到這個編譯錯誤:

mismatched types: expected Base , found a structure pattern

回答

21

你可以「T。特徵不支持向下轉換 - Rust不是繼承/子類型爲基礎的語言,它爲您提供了另一組抽象。此外,你想要做的是不完善的 - 特徵是開放的(每個人都可以實現它們),所以即使在你的情況下match *f涵蓋了所有可能的情況,但編譯器通常不知道這一點。

您有兩種選擇。如果您事先知道實現您的特徵的結構集合,只需使用枚舉,那麼這是一個完美的工具。它們允許您以靜態匹配一個封閉的變種:

enum FooBar { 
    Foo(uint), 
    Bar(uint) 
} 

fn test(v: bool) -> FooBar { 
    if v { 
     Foo(5) 
    } else { 
     Bar(10) 
    } 
} 

fn main() { 
    let f: FooBar = test(true); 

    // Now that we have a `Box<Base>` (`*f` makes it a `Base`), 
    // let's handle different cases: 
    match f { 
     Foo(x) => println!("it was Foo: {}!", x), 
     Bar(y) => println!("it was Bar: {}!", y), 
    } 
} 

(嘗試here

這是迄今爲止最簡單的方式,它應該始終是首選。

另一種方法是使用Any特質。它是類型安全的向下轉換到正規類型的設備從特質對象:

use std::any::{Any, AnyRefExt}; 

struct Foo { x: uint } 
struct Bar { y: uint } 

fn test(v: bool) -> Box<Any + 'static> { // ' 
    if v { 
     box Foo { x: 5 } 
    } else { 
     box Bar { y: 10 } 
    } 
} 

fn main() { 
    let f: Box<Any> = test(true); 

    match f.downcast_ref::<Foo>() { 
     Some(&Foo { x }) => println!("it was Foo: {}!", x), 
     None => match f.downcast_ref::<Bar>() { 
      Some(&Bar { y }) => println!("it was Bar: {}!", y), 
      None => unreachable!() 
     } 
    } 

// it will be nicer when `if let` lands 
// if let Some(&Foo { x }) = f.downcast_ref::<Foo>() { 
//  println!("it was Foo: {}!", x); 
// } else if let Some(&Bar { y }) = f.downcast_ref::<Bar>() { 
//  println!("it was Bar: {}!", y); 
// } else { unreachable!() } 
} 

(嘗試here

理想的情況下,應該可以寫這樣的事:

trait Base : Any {} 

impl Base for Foo {} 
impl Base for Bar {} 

然後在代碼中使用Base,但現在無法完成,因爲特徵繼承不適用於特徵對象(例如,不可能從Box<Base>Base<Any>)。

+0

我能用枚舉的命名結構領域?我真正的結構包含許多帶有名稱和幾種方法的字段。 – 2014-09-30 19:47:06

+0

您可以將任意數據放入enum變體中。這可以工作,例如:'struct Foo {f:uint};枚舉FooBar {EFoo(Foo)}'。枚舉還支持其變體中的字段(稱爲結構變體):'enum FooBar {Foo {f:uint}}',儘管這個特性是門控的,這只是一個語法上的便利 - 結構變體不是結構體,不能方法,例如。 – 2014-09-30 19:49:30

+0

好吧,Rust *具有*在運行時檢查類型的能力 - 它通過'Any'特性暴露。 'AnyRefExt'具有'is :: ()'方法,它與'instanceof'基本相同。但是,Rust不贊成靜態類型檢查。編寫一個基於枚舉的匹配調度是安全的,因爲它可以保證處理所有可能的情況,並且可以進行靜態檢查。 – 2014-10-01 14:20:44

3

你可以用我match_cast箱:

match_cast!(any { 
    val as Option<u8> => { 
     format!("Option<u8> = {:?}", val) 
    }, 
    val as String => { 
     format!("String = {:?}", val) 
    }, 
    val as &'static str => { 
     format!("&'static str = {:?}", val) 
    }, 
}); 

match_down!(any { 
    Bar { x } => { x }, 
    Foo { x } => { x }, 
}); 
+3

它被認爲是很好的禮儀[當你推薦你自己創建的東西時透露](http://meta.stackexchange.com/q/15787/281829) – Shepmaster 2016-09-23 13:29:35