【问题标题】:Possible to combine assignment and comparison in an expression?可以在表达式中结合赋值和比较吗?
【发布时间】:2016-11-03 09:16:13
【问题描述】:

在 C 中,通常在单个表达式中进行赋值和比较:

n = n_init;
do {
    func(n);
} while ((n = n.next) != n_init);

据我了解,这可以用 Rust 表示为:

n = n_init;
loop {
    func(n);
    n = n.next;
    if n == n_init {
        break;
    }
}

这与 C 版本的工作方式相同(假设循环体不使用continue)。

在 Rust 中是否有更简洁的方式来表达这一点,或者上面的示例是否理想?

就本问题而言,假设所有权或满足借用检查器不是问题。满足这些要求取决于开发人员。

例如,作为整数:

n = n_init;
loop {
    func(&vec[n]);
    n = vec[n].next;
    if n == n_init {
        break;
    }
}

这似乎很明显,Rust 示例是惯用的 Rust - 但是我希望将相当多的这种循环风格转移到 Rust,我很想知道是否有更好/不同的方式来表达它.

【问题讨论】:

  • 我会考虑将模式包装到迭代器中,因此您可以执行以下操作:for n in some_iter(n_init) { func(n) }
  • 能否请您发布完整的示例。每次您发布 sn-p 时,我们都必须猜测所涉及的类型可能是什么。他们是Copy吗?他们对某事拥有所有权吗?我们不知道!我们不知道!
  • 这个问题被标记为:Questions seeking debugging help,有人能解释一下为什么吗? - 它的目的是关于如何表达流量控制的问题。这里没有要调试的错误。
  • @ideasman42:问题是这使得 answerers 的工作变得更加困难,因为 ownership 是 Rust 的核心理念,因此答案取决于您使用仿射类型、借用还是Copy 类型。这就像你的最后一个问题,事后添加test() 第一次回答false 的条件......如果从一开始就将其编码在代码中,我本可以知道我的答案不适合。但由于我无法读懂您的想法,因此我提供了一个合适的答案,而您更改了问题后不再合适。所以..请完成。

标签: rust control-flow


【解决方案1】:

在 Rust 中表示迭代的惯用方式是使用 Iterator。因此,您将实现一个执行 n = n.next 的迭代器,然后使用 for 循环来迭代该迭代器。

struct MyIter<'a> {
    pos: &'a MyData,
    start: &'a MyData,
}
impl<'a> Iterator for MyIter<'a> {
    type Item = &'a MyData;
    fn next(&mut self) -> Option<&'a MyData> {
        if self.pos as *const _ == self.start as *const _ {
            None
        } else {
            let pos = self.pos;
            self.pos = self.pos.next;
            Some(pos)
        }
    }
}

将这个迭代器调整为能够从第一个元素开始而不是从第二个元素开始,留给读者作为练习。

【讨论】:

    【解决方案2】:

    Rust 支持 ifwhile 中的模式匹配:

    • 如果模式匹配,则认为测试成功,而不是布尔条件
    • 作为模式匹配的一部分,您将匹配的值绑定到名称

    因此,如果您构建的不是布尔条件,而是 Option...

    fn check(next: *mut Node, init: *mut Node) -> Option<*mut Node>;
    
    let mut n = n_init;
    loop {
        func(n);
        if let Some(x) = check(n.next, n_init) {
            n = x;
        } else {
            break;
        }
    }
    

    但是,如果您可以使用 Iterator 代替,您会更加惯用。

    【讨论】:

    • 虽然很有趣(在某些情况下可能是一个很好的解决方案),但这听起来像是一个迂回和间接的答案,这使得流控制不太清楚,然后在达到初始值时简单地中断。跨度>
    • @ideasman42:完全同意,但如果你想在一个条件下赋值,那是我能想到的唯一模式(我不鼓励使用它)。
    【解决方案3】:

    Rust 中的赋值返回空元组。如果你对非惯用代码没问题,你可以将赋值结果与这样一个空元组进行比较,并使用逻辑连接来链接你的实际循环条件。

    let mut current = 3;
    let mut parent;
    
    while (parent = get_parent(current)) == () && parent != current {
        println!("currently {}, parent is {}", current, parent);
        current = parent;
    }
    
    // example function
    fn get_parent(x: usize) -> usize {
        if x > 0 { x - 1 } else { x }
    }
    
    // currently 3, parent is 2
    // currently 2, parent is 1
    // currently 1, parent is 0
    

    这样做的缺点是进入循环需要运行逻辑(你可以使用 C 的do {..} while(); 样式循环来避免)。

    您可以在 do-while 宏中使用这种方法,但可读性不是很好,此时重构可能更可取。无论如何,这就是它的样子:

    do_it!({
        println!("{}", n);
    } while (n = n + 1) == () && n < 4);
    

    这是宏的代码:

    macro_rules! do_it {
        ($b: block while $e:expr) => {
            loop {
                $b
                if !($e) { break };
            }
        }
    }
    

    【讨论】:

    • 这样的缺点是进入循环需要运行逻辑(这可以通过C的do {..} while();风格循环避免)。
    • 我已经用 do..while 示例更新了答案,但在这一点上,你离人迹罕至的地方还很远。
    • 问题在于$b 中的continue 语句将跳过最后的中断。
    • 是的,根据您的问题,这是假设块中没有 continue 。如果您也想避免这种情况,您可以将 $b 包装在一个固定的单次或两次迭代循环中,并尝试根据您在该循环中进行的其他分配来判断是否使用了继续或中断。
    猜你喜欢
    • 2018-04-01
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 2015-02-04
    • 2013-05-03
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    相关资源
    最近更新 更多