在Rust中,可以编写一个do-while样式循环:
loop {
something();
if !test() {
break;
}
}
请注意,使用do-while表单而不是while test() { something() }
的目的是test()
可能需要在 something()
之后运行。
这样可行,但是当逻辑包含在宏中时,使用continue
时会发生什么不太明显。它将跳过测试,可能进入无限循环:
macro_rules! loop_over_items {
($item:expr, $iter:ident, $code:block) => {
{
let first = $item.first;
let mut $iter = first;
loop {
$code
$iter = $iter.next;
if (first != $iter) {
break;
}
}
}
}
}
这适用于基本情况:
loop_over_items!(item, iter_elem, {
code_to_run;
});
但这可能会进入一个无限循环,乍一看并不是很明显:
loop_over_items!(item, iter_elem, {
if some_test() {
continue;
}
code_to_run;
});
在Rust中写一个支持continue
之后跳过逻辑的$code
的宏会有什么好方法?
答案 0 :(得分:1)
添加此答案以表明它可以检测未知代码块中的继续/中断,但它不是漂亮。
macro_rules! loop_over_items {
($item:expr, $iter:ident, $code:block) => {
{
let first = $item.first;
let mut $iter = first;
loop {
{
let mut loop_state = false;
loop {
if loop_state == true {
break; // continue found in 'code'
}
loop_state = false;
$code
if loop_state {} // quiet unused warning
loop_state = true;
break;
}
if loop_state == false {
break; // break in 'code'
}
}
$iter = $iter.next;
if (first != $iter) {
break;
}
}
}
}
}
请注意,我很想知道使用此方法的一些影响:
body
中使用了传递到宏(它应该是可能的),则会对中断/继续检查进行优化。答案 1 :(得分:1)
为了扩展ideasman42's own answer中的想法,在测试是否包含break
或continue
时执行循环迭代的逻辑本身可以封装在宏中:
enum LoopIteration {
Normal,
Break,
Continue,
}
macro_rules! exec_iteration {
($code: block) => {{
let mut _state = LoopIteration::Normal;
loop {
if let LoopIteration::Break = _state {
// got back here after having preparing for break - it
// means a continue happened
_state = LoopIteration::Continue;
break;
}
// prepare for break
_state = LoopIteration::Break;
$code;
// neither break nor continue occurred
_state = LoopIteration::Normal;
break;
}
_state
}}
}
有了这个,支持break
和continue
的一般do-while宏可以写成如下:
macro_rules! do_while {
($code: block, $test: expr) => {{
loop {
match exec_iteration!($code) {
LoopIteration::Normal => (),
LoopIteration::Break => break,
LoopIteration::Continue => continue,
}
if !$test {
break;
}
}
}}
}
以上内容不是很有用,因为我们可以简单地将$code
嵌入到循环中,效果相同。但是在实现像loop_over_items
这样的宏时,现在有一种方法可以在$code
之后移动到下一个项目,即使它使用continue
:
macro_rules! loop_over_items {
($item:expr, $iter:ident, $code:block) => {{
let first = $item.first;
let mut $iter = first;
loop {
if let LoopIteration::Break = exec_iteration!($code) {
break
}
// Advance the iterator before proceeding, even if
// "continue" was used
$iter = $iter.next;
if first != $iter {
break;
}
}
}}
}