如何分组' Option' Rust的作业?

时间:2016-12-06 01:06:10

标签: rust variable-assignment

我有一段代码,需要一次分配多个可选变量。任何值都很少None,所以单独处理每个失败的案例并不是特别有用。

目前我写这样的支票:

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

值得注意的一些细节:

失败的第一个功能应该短路而不能呼叫其他功能。否则,可以这样写:

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

5 个答案:

答案 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

答案 1 :(得分:4)

标准库不包含该确切功能,但该语言允许您使用小宏创建所需的行为。

以下是我提出的建议:

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

如果所有值均为Some,则可以提供所有选项,并获取包含未展开值的元组,如果任何选项为None,则为None。< / p>

以下是如何使用它的简短示例:

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

答案 2 :(得分: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"),
    }
}

答案 3 :(得分:1)

老实说,有人应该注意Option是一个应用函子:)

如果没有在Rust中支持,代码将会非常丑陋,但它可以工作,并且它不应该产生嘈杂的差异:

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"),
}

答案 4 :(得分:1)

您可以使用“?”对值进行分组运算符返回具有所需值的元组的 Option。如果 on of then 是 Nonegroup_options 函数将返回 None

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

fn group_options() -> Option<(u8, u8, u8)> {
    let a = foo_a()?;
    let b = foo_b()?;
    let c = foo_c()?;
    Some((a, b, c))
}

fn main() {
    if let Some((a, b, c)) = group_options() {
        println!("{}", a);
        println!("{}", b);
        println!("{}", c);
    }
}