【问题标题】:What's the most idiomatic Rust way to modify an optional element of a Vec conditionally?有条件地修改 Vec 的可选元素的最惯用的 Rust 方法是什么?
【发布时间】:2017-12-21 06:50:51
【问题描述】:

我对编写以下非常常见的代码的最佳方式感到困惑:

let old_best = best_by_pos[y][x].as_ref();
if old_best.is_none() || &new_cost < old_best.unwrap() {
    best_by_pos[y][x] = Some(new_cost.clone());
}

这只是一个代码示例,但它说明了问题。

best_by_pos 是一个Vec&lt;Vec&lt;Option&lt;BigInt&gt;&gt;&gt;;当我们在该点找到最佳成本的新可能性时,我们想要 (a) 检查新成本是否优于旧成本,并且 (b) 如果是,则更新向量。

问题是old_best 不可变地借用best_by_pos,并且这种借用一直持续到作用域结束。这可以防止 if 块内的突变。理想情况下,我想在测试后立即释放old_best,但目前尚不清楚如何做到这一点。

有一种非常丑陋的方式来做到这一点——创建一个更深的范围来进行测试并公开一个布尔值,然后在此基础上做一个条件。这很实用,但令人不快。

或者,我可以创建一个辅助方法来进行比较(并在它终止时释放它的借用),它看起来更干净,但仍然感觉臃肿。

有没有更简洁的方法来实现这一点?

【问题讨论】:

    标签: rust idioms borrow-checker


    【解决方案1】:

    您可以使old_best 成为向量的可变引用,并在赋值中写入。这也使您可以避免再次索引向量:

    let old_best = &mut best_by_pos[y][x];
    if old_best.is_none() || &new_cost < old_best.as_ref().unwrap() {
        *old_best = Some(new_cost.clone());
    }
    

    【讨论】:

      【解决方案2】:

      如果您必须使用Vec,这样的事情可以避免任何明确的unwraps:

      let slot = &mut best_by_pos[y][x];
      let is_better = slot.as_ref().map_or(true, |old_cost| &new_cost < old_cost);
      if is_better {
          *slot = Some(new_cost.clone());
      }
      

      这仍然在向量中保留一个可变借用,因此您需要将它包装在一个范围内。

      另一种可能性是一些不太常见的模式语法:

      match best_by_pos[y][x] {
          ref mut entry @ None => *entry = Some(new_cost.clone()), 
          Some(ref mut entry) => {
              if &new_cost < entry {
                  *entry = new_cost.clone();
              }
          }
      }
      

      猜测,根据向量中Option 的使用情况,我建议您不要使用Vec。相反,HashMap 可以更好地表示稀疏数组的概念。此外,您还可以使用 Entry API:

      use std::collections::HashMap;
      use std::collections::hash_map::Entry;
      
      let mut best_by_pos: HashMap<(usize, usize), BigInt> = Default::default();
      
      match best_by_pos.entry((x, y)) {
          Entry::Vacant(e) => {
              e.insert(new_cost.clone());
          }
          Entry::Occupied(mut e) => {
              if &new_cost < e.get() {
                  e.insert(new_cost.clone());
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        Option&lt;T&gt; 为所有类型T 实现Ord,它实现Ord,这样None 对于任何v 都小于Some(v)。您可以这样编写代码:

        if best_by_pos[y][x].is_none() || Some(&new_cost) < best_by_pos[y][x].as_ref() {
            best_by_pos[y][x] = Some(new_cost.clone());
        }
        

        考虑到interjay的回答,也可以写成

        let cost = &mut best_by_pos[y][x];
        if cost.is_none() || Some(&new_cost) < cost.as_ref() {
            *cost = Some(new_cost.clone());
        }
        

        【讨论】:

        • 您并没有真正解决 OP 的问题:您必须多次写入 best_by_pos[y][x] 以避免借用,但 OP 正是借用了该值以避免这样做。使用OptionPartialOrd 实现是一个正交问题。
        • 从技术上讲,所述问题是持续时间过长的借用和代码膨胀。对于第二部分,Option&lt;T&gt;Ord 实现不是正交的。
        • 这与 OP 关于避免借用检查器问题的问题 100% 无关。不过,这将是一个很好的评论。顺便说一句,您缺少clone()。如果你在第一行添加clone(),它会损害代码相对于原始代码的性能。
        • 公平点。关于clone() 的重点是。我已经相应地编辑了我的答案。
        • 所以现在你的答案唯一改变的是删除一个unwrap并添加一个额外的Some。究竟有什么好处?这如何使代码不那么臃肿?
        【解决方案4】:

        HashMap可以相当简洁,如果你使用其他Entry方法,and_modifyor_insert

        use std::collections::HashMap;
        
        fn main() {
            let mut best_pos: HashMap<(usize, usize), f64> = Default::default();
            let newprice = 60.0;
        
            best_pos
                .entry((1, 2))
                .and_modify(|e| *e = e.min(newprice))
                .or_insert(newprice);
        }
        

        【讨论】:

          猜你喜欢
          • 2015-05-11
          • 1970-01-01
          • 2019-05-23
          • 2019-04-06
          • 2014-11-29
          • 2022-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多