有条件地修改Vec的可选元素的最惯用的Rust方法是什么?

时间:2017-07-16 18:57:57

标签: rust idiomatic borrow-checker

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

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_posVec<Vec<Option<BigInt>>>;当我们找到当时最佳成本的新可能性时,我们希望(a)检查新成本是否优于旧成本,以及(b)如果是,则更新向量。

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

有一种非常难看的方法 - 制作一个更深的范围来进行测试并公开一个布尔值,然后对此做一个条件。这是功能性但令人不快。

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

有没有更清洁的方法来实现这一目标?

4 个答案:

答案 0 :(得分:4)

您可以使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());
}

答案 1 :(得分:2)

如果您必须使用Vec,则此类内容可避免任何明确的unwrap

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());
        }
    }
}

答案 2 :(得分:1)

Option<T>为实施Ord的所有类型T实施OrdNone小于Some(v)任何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());
}

答案 3 :(得分:0)

HashMap可能非常简洁,如果您使用其他Entryand_modify方法,请使用以下方法:

or_insert