如何在Rust中跳出do-while循环?

5
Rust允许使用一种do-while循环,例如:
C语言的样式为:
do {
    something();
} while (test());

可以用Rust编写如下代码:
while {
    something();
    test()
}{}

然而,在这种情况下使用break存在一个问题:

所以这段C代码:

do {
    if (something()) {
        break;
    }
} while (test());

无法用Rust编写:

while {
    if (something()) {
        break;
    }
    test()
}{}

无法编译,报错信息为cannot break outside of a loop

有没有办法跳出这种形式的while循环?


注1)在这种情况下使用 do {...} while test() 流程控制,而不是 while test() {...} 的原因是,当进入循环时,test() 将为 false

注2)当使用 while {...} 流程控制时,在代码主体中调用 continue 将跳过末尾的 break 检查。

参见相关问题:如何在宏中包装 do-while 样式的循环,保持 'continue' 流程控制?


4
为什么不使用 loop { /* 代码 */ if test() { break; } } - Neikos
10
我必须说,我觉得这是一个可怕的hack(链接见https://gist.github.com/huonw/8435502)。第一组大括号实际上不是while循环体,而是**条件**,这就是为什么你无法跳出的原因。`while { break } {}`没有任何意义,这就是为什么这种语法可能更有害而不是有帮助的原因。 - Aurora0001
2
@Aurora0001 有人的黑客是别人的模式。 :) 我曾经看到过这种风格在各种缺少本地 do-while(例如 Emacs Lisp)的 Lisp 方言中使用,一旦你习惯它,它就很容易阅读。我同意,一旦需要 breakcontinue,最好切换到具有自定义放置断点逻辑的 loop - user4815162342
2
@user4815162342,也许是这样,但在我看来,更好的做法是“防御性编程”,假设阅读你代码的程序员不如你聪明,并且不会注意到模式。如果没有指出并思考它,我肯定不会知道发生了什么。 - Aurora0001
3
@Aurora0001 我同意防御性编程的观点——就像人们所说的,“编写代码时,要假设下一个维护者是一个疯子,他知道你住在哪里。” 另一方面,有些非常有用的习惯用语初看起来像垃圾,但一段时间后人们会学会识别、使用甚至欣赏它们。(例如,Rust 一开始看上去很奇怪的特性之一就是使用 ; 来确定是否返回一个值。) Rust 仍然是一门年轻的语言,还有待观察哪些模式足够有用,被社区认可为习惯用语。 - user4815162342
显示剩余2条评论
4个回答

5
如评论中所提到的,理解这种模式的工作原理很重要。它不是一种特殊形式的while,它只是滥用了循环测试来执行通常在循环体内完成的操作。由于breakcontinue不属于循环测试(除非循环是另一个循环的一部分,在这种情况下它们将被编译,但会中断/继续外部循环),Rust会拒绝它们。
通过将测试代码移到闭包中并使用return退出,可以直接模拟使用上述模式实现break的方法。
while (|| {
    if something() {
        return false // break
    }
    test()
})() {}

continue 可以通过从闭包中返回 true 来实现相同的效果。

如果这个过程被表达为一个宏,这可能会使意图更加清晰。


2
使用 rust 时,您可以在声明循环时标记它,并稍后使用该标记打破循环。
'mylabel: loop {
  something();
  if test() { break 'mylabel; }
}

2

您在想得太多了。

让我们建立一个真值表:

+-------------+--------+--------+
| something() | test() | Result |
+-------------+--------+--------+
|    true     |  true  |  stop  |
+-------------+--------+--------+
|    true     |  false |  stop  |
+-------------+--------+--------+
|    false    |  true  |  go on |
+-------------+--------+--------+
|    false    |  false |  stop  |
+-------------+--------+--------+

因此,这可以写成:
while !something() && test {}

不需要使用break

3
我不能使用这种逻辑的原因是 test 在第一次进入循环时为 false,我已经将此作为问题的注释添加进去了。 - ideasman42

1
一个符合你提议的有效解决方案可能是:
while {
    // bloc
    test()
} {
    // break conditions belonging here
    if (something()) {
        break;
    }
}

一些备注:

  • 第一个括号必须是表达式,Rust允许用户在其前面编写语句,并且整个带有test()bloc被视为表达式,但是正如您所指出的,存在一些限制。
  • 一个良好构建的循环通常避免使用break和continue语句,这种解决方案的优点在于将循环流程逻辑与仅在第二个括号中异常使用的添加控制逻辑分离,就像它们是循环外的参数一样。
  • 这次在test()之后调用了something(),这意味着它将无法打破循环的第一次迭代。

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