2016-12-06 55 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