如何在宏中包装do-while样式循环,保持“继续”流控制?

时间:2016-11-01 19:17:17

标签: macros rust

在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的宏会有什么好方法?

2 个答案:

答案 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中使用了传递到宏(它应该是可能的),则会对中断/继续检查进行优化。
  • 当检测中断/继续的逻辑适用时 - 它是否与在while循环中使用continue / break一样高效(do-while样式意味着它不会完全相同)
  • 当代码块中没有使用break / continue时,这些检查完全针对发布版本进行了优化,因此可以使用此宏而不会增加不必要的开销。

See real-world use of this example.

答案 1 :(得分:1)

为了扩展ideasman42's own answer中的想法,在测试是否包含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这样的宏时,现在有一种方法可以在$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;
            }
        }
    }}
}