归还已拥有资源的借用

3
我试图编写一个函数,将一个 Arc<[T]> 映射到一个 Iterable 中,以便与 flat_map 一起使用(也就是说,我想为某个其他的 i: Iterator<Item=Arc<[T]>> 调用 i.flat_map(my_iter))。
fn my_iter<'a, T>(n: Arc<[T]>) -> slice::Iter<'a, T> {
    let t: &'a [T] = &*n.clone();
    t.into_iter()
}

上述函数无法正常工作,因为n.clone()会产生一个Arc<[T]>类型的拥有值,我可以对它进行解引用以获取[T]并借用得到&[T],但是这个借用的生命周期只在函数结束之前存在,而'a生命周期会一直持续到客户端放弃迭代器。
如何克隆Arc以使客户端取得克隆的所有权,从而在客户端完成迭代器后才删除它(假设没有其他人在使用该Arc)?
以下是源迭代器的示例代码:
struct BaseIter<T>(Arc<[T]>);

impl<T> Iterator for BaseIter<T> {
    type Item = Arc<[T]>;

    fn next(&mut self) -> Option<Self::Item> {
        Some(self.0.clone())
    }
}

我该如何实现 BaseIter(data).flat_map(my_iter) 的结果(类型为 Iterator<&T>),鉴于 BaseIter 不仅是借用数据,而且还要生产数据?(实际情况比这更复杂,结果不总是相同的,但所有权语义相同。)
3个回答

5

您不能这样做。请记住,在Rust中,生命周期仅在编译时存在,仅用于验证代码是否意外访问已释放的数据。例如:

fn my_iter<'a, T>(n: Arc<[T]>) -> slice::Iter<'a, T>

在这里,'a无法“一直持续到客户端放弃返回的迭代器”; 这种推理是不正确的。从slice :: Iter的角度来看,其生命周期参数意味着它所指向的切片的生命周期;从my_iter的角度来看,'a只是一个可以由调用者任意选择的生命周期参数。换句话说,slice :: Iter始终与某个具有某些“具体”生命周期的切片相联系,但是my_iter的签名声明它能够返回“任意”生命周期。你看到矛盾了吗?
另外需要注意的是,由于生命周期的协变性,您可以从此类函数返回静态切片的切片:
static DATA: &'static [u8] = &[1, 2, 3];

fn get_data<'a>() -> &'a [u8] {
    DATA
}

上述定义虽然能够编译通过,但仅因为DATA存储在程序的静态内存中,且在程序运行时始终有效;但是对于Arc<[T]>来说并非如此。 Arc<[T]>意味着共享所有权,即Arc<[T]>中的数据由原始Arc<[T]>值的所有克隆对象共同拥有。因此,当最后一个克隆的Arc超出其范围时,它所包含的值就会被丢弃,并释放相应的内存。现在,请考虑如果my_iter()可以编译会发生什么:
let iter = {
    let data: Arc<[i32]> = get_arc_slice();
    my_iter(data.clone())
};
iter.map(|x| x+1).collect::<Vec<_>>();

因为在 my_iter() 中,'a 可以为任意值,并且与 Arc<[T]> 没有任何关联(实际上也不可能有),所以这段代码没有任何编译问题——用户可以选择使用 'static 生命周期。然而,在此代码块中,所有 data 的克隆都将在块内被删除,并且其中包含的数组也将被释放。在块之后使用 iter 是不安全的,因为现在它提供了对已释放的内存的访问。

我该如何克隆 Arc,以使客户端拥有克隆体的所有权,以便只有当客户端完成迭代器操作后才会删除该值(假设没有其他人使用 Arc)?

因此,根据上述内容,这是不可能的。只有数据的所有者决定何时应销毁此数据,而借用引用(其存在始终由生命周期参数暗示)仅可以在数据存在时借用该数据,但借用不能影响数据的销毁时间和方式。为了使借用引用编译通过,它们需要始终只借用整个引用有效期内有效的数据。
你可以重新思考你的架构。很难说没有查看完整代码的情况下可以做什么,但在这个特定示例中,你可以首先将迭代器收集到向量中,然后对向量进行迭代。
let items: Vec<_> = your_iter.collect();
items.iter().flat_map(my_iter)

注意,现在my_iter()确实应该接受&Arc<[T]>,就像Francis Gagné建议的那样;这样,输出迭代器的生命周期将与输入引用的生命周期绑定,一切都应该正常工作,因为现在可以保证Arc在向量中稳定存储,以便在迭代过程中稍后查看。

感谢这篇启发性的文章,它帮助我解决了问题。我意识到在这种情况下我过度使用了 Arc;迭代器不应该使用 Arc,因为它们不拥有数据 - 客户端已经拥有 data 字段,迭代器只需要从中借用即可。(我的数据结构是一种具有共享子节点的二叉树,因此我认为在数据结构中的子链接上仍然适用 Arc,但是对其进行迭代的迭代器不应控制数据。) - Mario Carneiro
@MarioCarneiro,是的,你的总结是正确的。当原始结构被消耗时,迭代器才会拥有它们后面产生的数据,就像使用Vec::into_iter()一样,否则引用更合适。 - Vladimir Matveev

2

如果要传递Arc<[T]>的值,将无法使其工作。您需要从Arc<[T]>的引用开始,以构建有效的slice::Iter

fn my_iter<'a, T>(n: &'a Arc<[T]>) -> slice::Iter<'a, T> {
    n.into_iter()
}

或者,如果我们省略生命周期:

fn my_iter<T>(n: &Arc<[T]>) -> slice::Iter<T> {
    n.into_iter()
}

1
嗯,这是个问题。我必须使用Arc <[T]>作为输入,因为它来自于一个Iterator<Item=Arc<[T]>>。我不能真正将其更改为Iterator<Item=&Arc<[T]>>,因为源通过从不可变后备向量克隆子切片并释放它们来创建值,并依靠Arc在引用用尽时清理后备向量。以这种方式使用&Arc <[T]>似乎只会将问题推迟到未来。 - Mario Carneiro
我为源迭代器添加了一些代码。您如何修改它以生成&Arc<[T]>数据? - Mario Carneiro

1

你需要使用另一个迭代器作为函数 my_iter 的返回类型。 slice::Iter<'a, T> 有一个关联类型 Item = &'a T。你需要一个关联类型为 Item = T 的迭代器。可以使用类似于 vec::IntoIter<T> 的迭代器。你可以自己实现这样的迭代器:

use std::sync::Arc;

struct BaseIter<T>(Arc<[T]>);

impl<T> Iterator for BaseIter<T> {
    type Item = Arc<[T]>;

    fn next(&mut self) -> Option<Self::Item> {
        Some(self.0.clone())
    }
}

struct ArcIntoIter<T>(usize, Arc<[T]>);

impl<T:Clone> Iterator for ArcIntoIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.0 < self.1.len(){
            let i = self.0;
            self.0+=1;
            Some(self.1[i].clone())
        }else{
            None
        }    
    }
}

fn my_iter<T>(n: Arc<[T]>) -> ArcIntoIter<T> {
    ArcIntoIter(0, n)
}

fn main() {
    let data = Arc::new(["A","B","C"]);
    println!("{:?}", BaseIter(data).take(3).flat_map(my_iter).collect::<String>());
    //output:"ABCABCABC"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接