Rust中的早期匹配退出

17
我希望能够通过许多可能的情况来切换 x,并且有一种情况(这里是 x == 0),我想要检查一些额外代码的结果以确定接下来该做什么。一种可能性是从匹配中提前返回。
在C中,我会使用break来进行早期返回,但在Rust中不允许这样做。 return从父函数(在本例中为main())返回,而不仅仅是从匹配中返回(即最后的println!不会执行!)。
我可以只否定子条件(这里是y == 0),并缩进所有随后的代码 - 但我认为这很丑陋和难以阅读。
将子条件放入匹配保护中对我来说不是一个选项,因为它太大了。
在Rust中是否可能实现这一点,或者是否有更好的替代方案(除了创建另一个子函数或其他解决方法)?
最小示例:
fn main() {
    let x = 1;

    match x {
        1 => {
            let y = 0;
            /*
             * do ev1l stuff to y that I don't want to put into the match-guard
             * as it's simply too much.
             */

            /* break early ... */
            if y == 0 {break;} // > error: `break` outside of loop [E0268]

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    }

    println!("done matching");
}

我找到了一篇关于Rust语言中“混合匹配、变异和移动”的文章Mixing matching, mutation, and moves in Rust,这样做是否正确?

match同时支持命令式和函数式编程风格:你可以继续使用break语句、赋值等操作,而不必强制采用表达式导向的思维方式。


7
读到这个问题时我的思维过程是:“创建一个else代码块。哦,原帖禁止了那个。也许可以用match守卫?哦,原帖禁止了那个。创建一个函数?哦,原帖禁止了那个。” - Shepmaster
1
我的问题是是否有类似于 break 的等效方法,因为在我看来,这是解决这个问题最“干净”的方式。当然,如果不可能,我会退而使用其他选项,但在我看来,这些选项对于我想要做的事情来说都是“过度杀伤力”的。 - ljrk
5
在你的限制条件下,无法完成此任务。 - WiSaGaN
2
这些对我想做的事情来说有点“过头”了。我不这么认为,使用函数简化匹配守卫是使代码更易读的好方法(当然,函数名称要有帮助性)。请记住,在你编写代码之后,其他人需要阅读你的代码。其他人包括未来的你,可能已经忘记了这里发生了什么。 - oli_obk
2
@larkey:将一个长但简单的代码片段缩短为一个(本地)函数(甚至是函数局部函数)绝对是提高可读性的一种方式。使用闭包甚至可以解决过多参数的问题。 - oli_obk
显示剩余9条评论
5个回答

10

这在某个时候发生了变化,现在您可以使用标签来跳出任何块!

例如:

fn main() {
    let x = 1;

    match x {
        1 => 'label: {
            let y = 0;

            /* break early ... */
            if y == 0 {
                break 'label;
            }

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    }

    println!("done matching");
}

10

你可以做的另一件事情是创建一个“自执行”闭包,并在其中使用return语句。我不知道这是否会有任何奇怪的性能特征,但从语法上来说,它非常干净。

fn main() {
    let x = 1;

    // This closure is just used to catch the "return" statement.
    (|| {
        match x {
            1 => {
                let y = 0;
                /*
                 * do ev1l stuff to y that I don't want to put into the match-guard
                 * as it's simply too much.
                 */

                /* break early ... */
                if y == 0 { return; } // Ok!

                assert!(y != 0, "y was 0!");
                /* do other stuff in here. */
            }
            _ => {}
        }
    })();

    println!("done matching");
}

这是一个展示其运行情况的游乐场链接

2
任何奇怪的性能特征——我希望这些都可以被 Rust 的“零成本抽象”所覆盖。 - Shepmaster
1
好主意,这让我想起了那些使用JavaScript自执行闭包技巧来隔离代码的方法! - Magix

8
你可以将match放入一个只运行一次并跳出循环的loop中进行包装。
fn main() {
    let x = 1;

    loop { match x {
        1 => {
            let y = 0;
            /*
             * do ev1l stuff to y that I don't want to put into the match-guard
             * as it's simply too much.
             */

            /* break early ... */
            if y == 0 { break; }

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    } break; }

    println!("done matching");
}

9
这是一个有趣的方法,但我认为它比应该更加混淆思想,而且不完全回答了我的问题。给你点赞,但我不能接受它 ;) - ljrk
3
为什么它不能回答你的问题?它让你跳出匹配,对吧?我问这个是因为我正在移植一个大型的 C 代码,有1000多行的 switch 语句,而这基本上就是我处理从匹配中跳出的方式。 - don bright
1
@donbright 这个解决方案是有效的,但我认为它会误导代码。如果我在实际应用中遇到这种情况,我会首先假设匹配被执行了多次,而事实并非如此。我需要花费相当长的时间来弄清楚这一点,之后我几乎肯定会尝试重构 loop 语句 - 假设它是由以前的修复留下的遗物。 - otoomey
我现在明白了,谢谢Camp bell。如果我不是在移植C语言,我永远不会使用这个技巧...我会重写它,以便一开始就更容易理解。 - don bright

7
您可以创建一个类似的宏:
macro_rules! block {
    ($xs:block) => {
        loop { break $xs }
    };
}

并执行

match x {
    1 => block!({
        ...
        if y == 0 { break; }
        ...
    })
    _ => {}
}

这不是一个惊人的解决方案,但它在语义上有意义。


@yolenoyer 我相信我的答案早于那个语法。我会更新的。 - Veedrac

2
答案是:你不能。
你试图这样做表明你没有编写符合语言习惯的Rust代码。
如果你绝对需要退出一个match,考虑将match放在一个返回值的函数中... 这样你可以更好地控制borrowing,相比于简单的代码块。然后将变量传递到match里面的函数中进行匹配。例如:
fn match_it(val: i8) -> i8 {
    let mut a: i8 = 0;
    match random_or(0, 1) {
        1 => {
                a = 5;
                // other ev1l stuff
                if a == 6 { return 1 }
             },
        0 => a = -5,
        _ => (),
    };
    
    a //return a
}

根据你的代码,你只需要交换两个语句的顺序就可以实现期望的控制流:

        if y == 0 {break;} // > error: `break` outside of loop [E0268]

        assert!(y != 0, "y was 0!");
        /* do other stuff in here. */

转化为:

        if y != 0 {
             assert!(y != 0, "y was 0!");
             /* do other stuff in here. */
        }

        // if we reach here, by definition one of these conditions is true: y == 0, or y != 0 and code above has run and completed.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接