如何在宏中包含一个do-while循环,同时保持'continue'流程控制?

3
在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后面逻辑的宏,您可以采用什么方法?

为什么这个问题被标记为关闭:“寻求调试帮助的问题”,这是一个关于流程控制的一般性问题(并且有答案展示如何完成)。 - ideasman42
2个回答

1
添加此答案以展示在未知代码块中检测continue/break是可能的,但这并不美观。
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中没有使用break/continue检查,那么这些检查是否会被优化掉(应该是可能的)
  • 当检测break/continue的逻辑应用时,它是否与在while循环中使用continue/break大致相同(do-while样式意味着不会完全相同)
  • 当代码块中未使用break/continue时,这些检查会在发布版本中完全被优化掉,因此可以使用此宏而不会增加不必要的开销。

查看此示例的实际用法。


1
一个绝妙的想法。我已经发布了一个答案,将不太美观的测试提取到一个单独的宏中,从而允许相对可读的loop_over_items定义。 - user4815162342

1

ideasman42的回答中的想法进行扩展,即在测试循环迭代是否包含breakcontinue的逻辑可以本身被封装在一个宏中:

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

有了这个,一个支持 breakcontinue 的通用 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这样的宏时,现在有一种方法可以在使用continue的情况下移动到下一个项目后$code
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;
            }
        }
    }}
}

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