【问题标题】:How to update-or-insert on a Vec?如何在 Vec 上更新或插入?
【发布时间】:2017-11-20 15:13:04
【问题描述】:

我正在用 Rust 编写一个数据结构。它包含一个Vec 的键值对。插入结构时,我需要找到一个匹配的键并更新键和值(实际上是一个子指针)。代码看起来有点像这样,其中pivotsref mutVec<Pivot>Pivot 只是一个包含两个字段的结构:

match pivots.iter_mut().find(|ref p| key <= p.min_key) { // first mutable borrow
    Some(ref mut pivot) => {
        // If there is one, insert into it and update the pivot key
        pivot.min_key = key;
        pivot.child.insert(key, value) // recursive call
    },
    // o/w, insert a new leaf at the end
    None => pivots.push(Pivot /* ... */) // second mutable borrow
}

但是有一个问题。即使我没有在match 的第二个分支中使用可变迭代器,借用检查器仍抱怨我“不能一次多次将*pivots 借用为可变”。

这对我来说非常有意义,因为第一个借用仍在范围内,即使在 match 的这种情况下没有使用它。这有点不方便:一个更聪明的检查器肯定可以判断出借用是不重叠的。我在网上看到有人建议使用提前退货来避免这个问题,像这样:

match pivots.iter_mut().find(|ref p| key <= p.min_key) {
    Some(ref mut pivot) => {
        pivot.min_key = key;
        pivot.child.insert(key, value);
        return
    },
    None => ()
};
pivots.push(Pivot /* ... */)

但这似乎很难理解,尤其是当这意味着将这段代码分解为它自己的函数以允许return 时。是否有更惯用的方式来执行更新或插入操作?

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    有一个合并的 RFC "non-lexical lifetimes" 从长远来看可以解决这个问题。使用 Rust 1.31 中可用的 Rust 2018 中的非词法生命周期,您的代码可以按原样工作:

    Playground

    use std::collections::HashMap;
    
    pub struct Pivot {
        pub min_key: u64,
        pub child: HashMap<u64, ()>,
    }
    
    fn update_or_append(pivots: &mut Vec<Pivot>, key: u64, value: ()) {
        match pivots.iter_mut().find(|ref p| key <= p.min_key) {
            Some(pivot) => {
                // If there is one, insert into it and update the pivot key
                pivot.min_key = key;
                pivot.child.insert(key, value);
                return;
            }
            // o/w insert a new leaf at the end
            None => {
                let mut m = HashMap::new();
                m.insert(key, value);
                pivots.push(Pivot {
                    min_key: key,
                    child: m,
                });
            }
        }
    }
    
    fn main() {
        let mut pivots = Vec::new();
        update_or_append(&mut pivots, 100, ());
    }
    

    如果这适用于您的代码,请查看


    在 Rust 2018 之前,您可以通过一些额外的控制流处理来解决它。

    无论更新是否发生,您都可以让您的匹配生成一个 bool 值,并在下方使用该值附加一个条件块。我考虑将“更新或附加”逻辑放入一个单独的函数中(在更新后使用return)更惯用的方法:

    Playground

    use std::collections::HashMap;
    
    pub struct Pivot {
        pub min_key: u64,
        pub child: HashMap<u64, ()>,
    }
    
    fn update_or_append(pivots: &mut Vec<Pivot>, key: u64, value: ()) {
        if let Some(pivot) = pivots.iter_mut().find(|ref p| key <= p.min_key) {
            // If there is one, insert into it and update the pivot key
            pivot.min_key = key;
            pivot.child.insert(key, value);
            return;
        }
        // otherwise insert a new leaf at the end
        let mut m = HashMap::new();
        m.insert(key, value);
        pivots.push(Pivot {
            min_key: key,
            child: m,
        });
    }
    
    fn main() {
        let mut pivots = Vec::new();
        update_or_append(&mut pivots, 100, ());
    }
    

    使用bool 跟踪更新是否发生:

    Playground

    use std::collections::HashMap;
    
    pub struct Pivot {
        pub min_key: u64,
        pub child: HashMap<u64, ()>,
    }
    
    fn update_or_append(pivots: &mut Vec<Pivot>, key: u64, value: ()) {
        let updated = match pivots.iter_mut().find(|ref p| key <= p.min_key) {
            Some(pivot) => {
                // If there is one, insert into it and update the pivot key
                pivot.min_key = key;
                pivot.child.insert(key, value);
                true
            }
            // o/w insert a new leaf at the end below
            None => false,
        };
        if !updated {
            let mut m = HashMap::new();
            m.insert(key, value);
            pivots.push(Pivot {
                min_key: key,
                child: m,
            });
        }
    }
    
    fn main() {
        let mut pivots = Vec::new();
        update_or_append(&mut pivots, 100, ());
    }
    

    【讨论】:

    • 问题中已经考虑了单独的功能选项。也许答案可以为第一个提议的解决方案提供源代码,其中 match 产生bool
    • 我经常跳过问题中的长文本并且没有意识到它们已经包含问题的(不需要的)答案,这可能是我的错误。 otoh 的答案不属于那里,所以在这种情况下我不会对此感到内疚。而且我认为单独的函数实际上更容易理解。
    【解决方案2】:

    似乎最好的方法是使用索引而不是迭代器。

    match pivots.iter().position(|ref p| key <= p.min_key) {
        Some(i) => {
            // If there is one, insert into it and update the pivot key
            let pivot = &mut pivots[i];
            pivot.min_key = key;
            pivot.child.insert(key, value)
        },
        // o/w, insert a new leaf at the end
        None => pivots.push(Pivot /* ... */)
    }
    

    这样,就不需要iter_mut。我仍然对这种替代方案并不完全满意,因为这意味着使用显式索引而不是迭代器。这对于 Vec 来说很好,但不适用于结构没有 O(1) 随机访问索引的容器。

    我会接受一个不同的答案,让我避免使用索引。

    【讨论】:

      猜你喜欢
      • 2019-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-16
      • 2022-12-21
      • 2021-01-06
      • 2023-04-02
      • 1970-01-01
      相关资源
      最近更新 更多