为什么 unwrap_or() 在范围内保留借用?

Why does unwrap_or() keep borrow in scope?

正在尝试编译:

fn main() {
    let mut vec = vec![1, 2, 3];
    let four: &mut u32 = borrow_or_add(&mut vec, 4);
}

fn borrow_or_add(vec: &mut Vec<u32>, val: u32) -> &mut u32 {
    vec.iter_mut().find(|v| **v == val).unwrap_or({
        vec.push(val);

        vec.last_mut().unwrap()
    })
}

Playground

...给出以下结果:

q.rs:8:9: 8:12 error: cannot borrow `*vec` as mutable more than once at a time
q.rs:8         vec.push(val);
               ^~~
q.rs:7:5: 7:8 note: previous borrow of `*vec` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*vec` until the borrow ends
q.rs:7     vec.iter_mut().find(|v| **v == val).unwrap_or({
           ^~~
q.rs:12:2: 12:2 note: previous borrow ends here
q.rs:6 fn borrow_or_add(vec: &mut Vec<u32>, val: u32) -> &mut u32 {
...
q.rs:12 }
        ^
q.rs:10:9: 10:12 error: cannot borrow `*vec` as mutable more than once at a time
q.rs:10         vec.last_mut().unwrap()
                ^~~
q.rs:7:5: 7:8 note: previous borrow of `*vec` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*vec` until the borrow ends
q.rs:7     vec.iter_mut().find(|v| **v == val).unwrap_or({
           ^~~
q.rs:12:2: 12:2 note: previous borrow ends here
q.rs:6 fn borrow_or_add(vec: &mut Vec<u32>, val: u32) -> &mut u32 {
...
q.rs:12 }
        ^
q.rs:10:9: 10:12 error: cannot borrow `*vec` as mutable more than once at a time
q.rs:10         vec.last_mut().unwrap()
                ^~~
q.rs:7:5: 7:8 note: previous borrow of `*vec` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*vec` until the borrow ends
q.rs:7     vec.iter_mut().find(|v| **v == val).unwrap_or({
           ^~~
q.rs:12:2: 12:2 note: previous borrow ends here
q.rs:6 fn borrow_or_add(vec: &mut Vec<u32>, val: u32) -> &mut u32 {
...
q.rs:12 }
        ^

据我所知,unwrap_or() 的主体不能引用提到的 vec 的借用,那么为什么它没有从作用域中删除?有没有一种优雅的方式来实现这个功能?如果不使用两次传递和 bool(即 contains()),我实际上无法找到成功执行此操作的方法。

问题是 知道,凭借其语义,vec.iter_mut().find().unwrap_or() 将忽略它来自的 vec 的任何内容。但是,编译器对此一无所知。实际上,方法调用链可以很好地存储来自迭代器的值,直到 unwrap_or() 调用的闭包。请参阅此常见场景,它具有与您的示例类似的结构:

let mut vec: Vec<i32> = vec![3, 2, 5];
vec.iter().map(|&i| { 
    vec.push(i+1)
}).last();

这里的vec.iter_mut().find().unwrap_or()只是被vec.iter().map()代替了。如您所见,可以在整个方法调用过程中借用 vec 中的值。在后一个示例中,很明显 vec.push(i+1) 如何使迭代器无效,这就是编译器阻止它的原因。

借用至少对整个语句有效。不幸的是,将 Option 拉出到单独的语句中的通常解决方案无济于事,因为它仍将包含一个可变引用(在 Some 中),这也迫使向量的借用被扩展.这在使用 find() 时似乎是不可避免的。编译器担心指向 Vector 存储的指针在某处浮动,并且可能会因推入 vector 而无效(这可能导致重新分配)。这不仅包括返回的 Option 中的指针,还包括可以由其中一个函数在内部创建的指针。如果不是因为没有 "hidden" 指针并且您仅在 Option 不是 指针时才推送,这将是一个非常有效的问题。生命周期系统不够精细,无法捕捉到这一事实,而且我没有看到修复或解决它的简单方法。

你可以做的是使用position找到一个索引:

fn borrow_or_add(vec: &mut Vec<u32>, val: u32) -> &mut u32 {
    match vec.iter().position(|&v| v == val) {
        Some(i) => &mut vec[i],
        None => {
            vec.push(val);
            vec.last_mut().unwrap()
        }
    }
}

请注意,向量中的索引本质上是美化的指针。如果向量发生变化,它们可能会开始引用错误的元素,尽管它们不会在重新分配时悬空,无效索引(大于最大有效索引)将导致恐慌而不是内存不安全。尽管如此,从 position 获取索引然后用它建立索引只是 正确的 因为向量在两者之间没有(实质上)修改。