2016-12-06 89 views
8

我有一個代碼塊需要一次分配多個可選變量。幾乎沒有機會獲得任何值,因此單獨處理每個失敗的案例並不是特別有用。如何在Rust中對「選項」分配進行分組?

目前我寫的檢查是這樣的:

if let Some(a) = foo_a() { 
    if let Some(b) = foo_b() { 
     if let Some(c) = foo_c() { 
      if let Some(d) = foo_d() { 
       // code 
      } 
     } 
    } 
} 

這將是方便,如果有可能小組作業。沒有這一點,增加了新的變數縮進塊一個水平,使得在嘈雜的比較和導致不必要的深壓痕:

if let Some(a) = foo_a() && 
    let Some(b) = foo_b() && 
    let Some(c) = foo_c() && 
    let Some(d) = foo_d() 
{ 
    // code 
} 

有if語句來分配多個Option S IN一個辦法?


一些細節值得注意:

失敗應該短路,而不是叫別人第一個函數。否則,它可以被寫成這樣:

if let (Some(a), Some(b), Some(c), Some(d)) = (foo_a(), foo_b(), foo_c(), foo_d()) { 
    // Code 
} 

可以用一個功能避免深的壓痕,但我寧願不要這樣做,因爲你可能不希望擁有的身在不同的範圍...

fn my_function(a: Foo, b: Foo, c: Foo, d: Foo) { 
    // code 
} 

if let Some(a) = foo_a() { 
    if let Some(b) = foo_b() { 
     if let Some(c) = foo_c() { 
      if let Some(d) = foo_d() { 
       my_function(a, b, c, d); 
      } 
     } 
    } 
} 
+2

我字面上大約爲[把這個作爲一個答案(HTTPS://play.rust-lang .org /?gist = 19b24cb31e915860916a99f41347b727&version = stable&backtrace = 0),直到我注意到包含短路的編輯。我不認爲它可能會短路多個'如果讓'綁定。 [但是有一個開放的RFC](https://github.com/rust-lang/rfcs/issues/929)。 –

+0

@SimonWhitehead,非常感謝,並將其添加到問題中以供澄清 - 因爲在某些情況下它仍然可能非常方便。 – ideasman42

回答

4

標準庫不包含該確切功能,但該語言允許您使用小的宏創建所需的行爲。

這就是我想出了:

macro_rules! all_or_nothing { 
    ($($opt:expr),*) => {{ 
     if false $(|| $opt.is_none())* { 
      None 
     } else { 
      Some(($($opt.unwrap(),)*)) 
     } 
    }}; 
} 

你可以給它所有的選擇,並得到一些元組包含展開的值,如果所有值都Some,或None在任何的選項的情況下None

以下是關於如何使用它的一個簡單的例子:

fn main() { 
    let foo = Some(0); 
    let bar = Some(1); 
    let baz = Some(2); 
    if let Some((a, b, c)) = all_or_nothing!(foo, bar, baz) { 
     println!("foo: {}; bar: {}; baz: {}", a, b, c); 
    } else { 
     panic!("Something was `None`!"); 
    } 
} 

這裏有一個完整的測試套件爲宏:Rust Playground

+1

很好的答案!我考慮了宏觀方法,但不知道如何去實現它(宏對我來說仍然有點嚇人!)。 –

+1

@SimonWhitehead老實說,我對宏也很陌生。當我意識到這確實有效時,你應該看到我的臉。 – SplittyDev

1

老實說,應該有人通知一下Option作爲一個適用函子: )

該代碼將是相當醜陋,沒有在鐵鏽支持,但它的工作原理,它不應該有一個嘈雜的差異:

fn foo_a() -> Option<isize> { 
    println!("foo_a() invoked"); 
    Some(1) 
} 

fn foo_b() -> Option<isize> { 
    println!("foo_b() invoked"); 
    Some(2) 
} 

fn foo_c() -> Option<isize> { 
    println!("foo_c() invoked"); 
    Some(3) 
} 

let x = Some(|v| v) 
    .and_then(|k| foo_a().map(|v| move |x| k((v, x)))) 
    .and_then(|k| foo_b().map(|v| move |x| k((v, x)))) 
    .and_then(|k| foo_c().map(|v| move |x| k((v, x)))) 
    .map(|k| k(())); 

match x { 
    Some((a, (b, (c,())))) => 
     println!("matched: a = {}, b = {}, c = {}", a, b, c), 
    None => 
     println!("nothing matched"), 
} 
+1

工程,但越來越多的爭論越來越糟糕。 – SplittyDev

4

我的第一個想法是做一些類似於swizard's answer的東西,但要將其包裹在一個特徵中以使鏈條變得更清潔。不需要額外的函數調用,它也更簡單一些。

它的缺點是增加了元組的嵌套。

fn foo_a() -> Option<u8> { 
    println!("foo_a() invoked"); 
    Some(1) 
} 

fn foo_b() -> Option<u8> { 
    println!("foo_b() invoked"); 
    None 
} 

fn foo_c() -> Option<u8> { 
    println!("foo_c() invoked"); 
    Some(3) 
} 

trait Thing<T> { 
    fn thing<F, U>(self, f: F) -> Option<(T, U)> where F: FnOnce() -> Option<U>; 
} 

impl<T> Thing<T> for Option<T> { 
    fn thing<F, U>(self, f: F) -> Option<(T, U)> 
     where F: FnOnce() -> Option<U> 
    { 
     self.and_then(|a| f().map(|b| (a, b))) 
    } 
} 

fn main() { 
    let x = foo_a() 
     .thing(foo_b) 
     .thing(foo_c); 

    match x { 
     Some(((a, b), c)) => println!("matched: a = {}, b = {}, c = {}", a, b, c), 
     None => println!("nothing matched"), 
    } 
} 
+0

我真的很喜歡這個說實話......我想我可以看到這種技術適用於我目前的工作。謝謝! –

10

作爲@SplittyDev said,您可以創建一個宏以獲得您想要的功能。這裏是一個替換的基於宏的解決方案,它也保留了短路行爲:

macro_rules! iflet { 
    ([$p:pat = $e:expr] $($rest:tt)*) => { 
     if let $p = $e { 
      iflet!($($rest)*); 
     } 
    }; 
    ($b:block) => { 
     $b 
    }; 
} 


fn main() { 
    iflet!([Some(a) = foo_a()] [Some(b) = foo_b()] [Some(c) = foo_c()] { 
     println!("{} {} {}", a, b, c); 
    }); 
} 

Playground

+0

真的很好,小疣是它需要'身體'是一個宏論據,這讓我更喜歡@ SplittyDev的答案。 – ideasman42

+0

@ ideasman42宏參數?據我所知,該機構可以是任意的代碼塊。我承認我對宏的限制並不是很熟悉,所以如果你有一個不適合這個宏的好例子,它肯定會是對未來訪問者的答案的一個很好的修正。 –

+0

@Eric,在宏中沒有錯誤 - 正如你所說的,它可以將任何代碼塊作爲參數,它只是讀有點尷尬,如果宏!(args,{body});'與'if macro !(args){body}'它並不是特別糟糕,只是我個人偏好避免它 - 在給出選擇的情況下,並且假定替代方案在其他方面並沒有更糟。 – ideasman42