创建 RW 守卫的迭代器时,借用的值寿命不够长

问题描述 投票:0回答:1

问题:我想要一个在守卫下的项目迭代器(在这种情况下为

RwLockReadGuard
)。这些项目不是参考,它们是克隆的。 好像很像那些问题:

但是也许这个问题还有其他的味道我无法理解。

所以下面有一个

StructWithRwLock
实现了一个
Iterator
。并且有一个结构
ManyStructWithRwLock
,它(令人惊讶地)包含一个结构
StructWithRwLock
的 vec。它还实现了一个迭代器。
ManyStructWithRwLock
的迭代器运行其子代的每个迭代器,并始终产生下一个最小元素。为此,使用了最小堆 (
BinaryHeap
) 结构。迭代器不允许在内部使用分配,因此它必须使用外部分配
BinaryHeap
.

最后有一个测试。在测试中创建了迭代器的两个副本。两者都落在两者之间。但是出于某种原因,编译器认为当二进制堆本身被删除时,可能会使用对二进制堆的可变引用。怎么会?所以我得到这个错误

error[E0597]: `test_struct` does not live long enough
   --> src/test_example.rs:132:24
    |
132 |         let mut iter = test_struct.iter(&mut buffer_heap);
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
142 |     }
    |     -
    |     |
    |     `test_struct` dropped here while still borrowed
    |     borrow might be used here, when `buffer_heap` is dropped and runs the destructor for type `BinaryHeap<StructWithRwLockIter<'_>>`
    |
    = note: values in a scope are dropped in the opposite order they are defined

为什么会这样?我需要不安全吗?如何纠正?

最小运行示例:

use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::{Arc, RwLock, RwLockReadGuard};

type BufferHeap<'b> = BinaryHeap<StructWithRwLockIter<'b>>;

#[derive(Debug)]
pub struct StructWithRwLock {
    inner: Arc<RwLock<Vec<usize>>>
}
impl StructWithRwLock {
    pub fn iter(&self) -> StructWithRwLockIter {
        let inner = self.inner.read().unwrap();
        StructWithRwLockIter {
            inner,
            current_index: 0,
        }
    }

}
pub struct StructWithRwLockIter<'a> {
    inner: RwLockReadGuard<'a, Vec<usize>>,
    current_index: usize,
}
impl<'a> StructWithRwLockIter<'a> {
    fn peek(&self) -> Option<usize> {
        let entries = &self.inner;

        if self.current_index >= entries.len() {
            return None;
        }

        let item = entries.get(self.current_index).unwrap();
        Some(*item)
    }
}

impl<'a> Eq for StructWithRwLockIter<'a> {}

impl<'a> PartialEq<Self> for StructWithRwLockIter<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.peek().eq(&other.peek())
    }
}

impl<'a> PartialOrd<Self> for StructWithRwLockIter<'a> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.peek().partial_cmp(&other.peek())
    }
}

impl<'a> Ord for StructWithRwLockIter<'a> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.peek().cmp(&other.peek())
    }
}

impl<'a> Iterator for StructWithRwLockIter<'a> {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        let entries = &self.inner;

        if self.current_index >= entries.len() {
            return None;
        }

        let item = entries.get(self.current_index).unwrap();
        self.current_index += 1;
        Some((*item).clone())
    }
}

pub struct ManyStructWithRwLock {
    items: Vec<StructWithRwLock>
}

impl ManyStructWithRwLock {
    pub fn iter<'b, 'a:'b>(&'b self, ext_buffer: &'a mut BufferHeap<'b>) -> impl Iterator<Item = usize> +'b {
        ext_buffer.clear();
        for item in &self.items {
            ext_buffer.push(item.iter());
        }

        kmerge(ext_buffer)
    }
}

/// similar to itertools::kmerge_by but using extartnal buffer
pub fn kmerge<'b,'a:'b>(ext_buffer: &'a mut BufferHeap<'b>) -> KMergeStrWLockBy<'a, 'b>
{
    KMergeStrWLockBy {
        heap: ext_buffer,
    }
}

pub struct KMergeStrWLockBy<'a, 'b>
{
    heap: &'a mut BufferHeap<'b>,
}

impl<'a, 'b> Iterator for KMergeStrWLockBy<'a, 'b>
{
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        let mut next = self.heap.pop()?;
        let item = next.next()?;
        self.heap.push(next);
        Some(item)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::BinaryHeap;

    #[test]
    fn test_aggr_iterator() {
        let mut buffer_heap = BinaryHeap::with_capacity(2);
        let test_struct = ManyStructWithRwLock {
            items: vec![
                StructWithRwLock {
                    inner: Arc::new(RwLock::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
                },
                StructWithRwLock {
                    inner: Arc::new(RwLock::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
                }
            ]
        };

        let mut iter = test_struct.iter(&mut buffer_heap);
        for i in 0..10 {
            let _ = iter.next().unwrap();
        }
        drop(iter);
        let mut iter = test_struct.iter(&mut buffer_heap);
        for i in 0..10 {
            let _  = iter.next().unwrap();
        }
        drop(iter);
    }
}
rust lifetime
1个回答
0
投票

这看起来像是通过将项目转发到

test_struct
的可变引用中来在
buffer_heap
中借用
buffer_heap
,而抱怨
test_struct
被删除得太早就是一个指标。即:

impl StructWithRwLock {
    pub fn iter(&self) -> StructWithRwLockIter {
        let inner = self.inner.read().unwrap();  // <-- mutable borrow of the vec inside Inner, requires a borrow of self
        StructWithRwLockIter {  // <-- therefore this returns an object that borrows self
            inner,
            current_index: 0,
        }
    }
}

Iterator 结构借用父结构是很正常的,因为它必须迭代元素。然而,在这里:

        // ManyStructWithRwLock
        for item in &self.items {   // <-- borrow of self to borrow items
            ext_buffer.push(item.iter());  // <-- pushes a reference within item, which therefore borrows item, which therefore borrows self
        }

不是将单个结构的克隆项推入借用的缓冲区,而是推入每个结构的 Iterator,迭代器如图所示借用

test_struct
,所以
buffer_heap
现在借用
test_struct
。迭代器在用完项目时不会自动删除,它们只是处于“完成”状态,并且在这里它们仍在堆中(而且我不确定借用检查器是否会理解你是否如果您迭代 11 而不仅仅是 10 或进行清除,则能够删除它们)。

你可以急切地从你的内部结构中克隆并将它们直接放在缓冲区中(但这是一个分配),或者将你的结构向量包装在

Rc
Cell
中并克隆它们——这仍然是一个用于复制指针的分配,但至少它没有分配向量的全部内容。或者您可以要求将缓冲区移动到
ManyStructWithRwLock::iter
中,这样它也由数据的迭代器拥有,因此在迭代器是时被删除。

作为 Rust 的新手,他反复遇到这个问题,试图对意外借用到其他对象的东西进行可变引用......试图在结构中保存引用非常难以维护。

© www.soinside.com 2019 - 2024. All rights reserved.