在Rust中将结构体的迭代器传递给接受引用的函数?

3

我有一个函数,它接受一个引用结构体的迭代器。有时我遍历一个向量,这很好,但有时我创建一个产生新结构体的迭代器,我很难弄清楚这个问题。我知道当我在闭包中创建一个值时,它会在闭包结束时消失。Rust总是试图将我不想移动的值从东西中移出来;为什么这里不这样做呢?

struct Thing {
    value: u32,
}

fn consume<'a, I: IntoIterator<Item = &'a Thing>>(things: I) {
    for thing in things {
        println!("{}", thing.value);
    }
}

fn main() {
    let solid = vec![Thing { value: 0 }];
    let ephemeral = (1..5).map(|i| &Thing { value: i }); // Boxing Thing does not work either
    consume(solid.iter());
    consume(ephemeral);
}

但是。
error[E0515]: cannot return reference to temporary value
  --> src/main.rs:13:36
   |
13 |     let ephemeral = (1..5).map(|i| &Thing { value: i }); // Boxing Thing does not work either
   |                                    ^------------------
   |                                    ||
   |                                    |temporary value created here
   |                                    returns a reference to data owned by the current function

我感觉我需要将结构体从闭包和迭代器中移出,或者将其存储在其他地方。但是将结构体放入Box中不起作用,返回结构体而不是指针无法通过类型检查(我找不到.cloned()的相反方法)。这里的方法是什么?

2个回答

9

简短回答:你无法做到。


更详细的解释:

这里是“生成新结构体的迭代器”:

let iterator_of_structs = (1..5).map(|value| Thing { value });

找出答案的关键是始终问“谁拥有这些数据?”每次调用next时,闭包通过value获取一个整数的所有权,并构造一个新的Thing。闭包返回Thing,将所有权转移给调用next的代码。当您借用值(即获取引用)时,该值的所有权不能转移且该值必须持续时间超过借用的持续时间。让我们转向引用迭代器的概念,并问自己:“谁拥有这些数据?”
map(|value| &Thing { value })

在这里,我们创建了一个Thing并获取了对它的引用。没有变量拥有Thing,因此作用域拥有它,并且当作用域结束时,该值将被销毁。闭包尝试返回引用,但这违反了借用项必须超出其借用的公理。
那么,如何解决呢?最简单的方法是将函数更改为更加接受:
use std::borrow::Borrow;

struct Thing {
    value: u32,
}

fn consume(things: impl IntoIterator<Item = impl Borrow<Thing>>) {
    for thing in things {
        let thing = thing.borrow();
        println!("{}", thing.value);
    }
}

fn main() {
    let iterator_of_structs = (1..5).map(|value| Thing { value });
    consume(iterator_of_structs);

    let vector_of_structs: Vec<_> = (1..5).map(|value| Thing { value }).collect();
    let iterator_of_references_to_structs = vector_of_structs.iter();
    consume(iterator_of_references_to_structs);
}

在这里,我们接受任何可以转换为允许我们借用对Thing的引用的项目迭代器的类型。这适用于任何项目和对项目的任何引用。

如果闭包返回的是实际结构而不是指针,那么所有权是否会移动到“map”迭代器上,然后在“next()”上将所有权移动给其调用者?那就只需要一个迭代器适配器,它接受一个东西并返回对它的引用。那似乎不是不可能的。为什么“Box”不能工作呢? - Doctor J
1
@DoctorJ 然后只需要有一个迭代器适配器,它接受一个东西并返回对它的引用 - 但是在引用被返回时,什么会持有该物品的所有权?这开始偏离delnan的答案Iterator特质不允许您返回对迭代器本身拥有的某些内容的引用。即使更改了特质(或创建了替代特质),它也将具有大量限制。当前的抽象在更多上下文中更适用(和可理解)。 - Shepmaster

3
一个引用的迭代器允许消费者保留迭代器所产生的所有引用,至少在迭代器本身仍然存在期间。显然,为了支持这种情况,迭代器创建引用的所有对象都需要同时在内存中。目前的迭代器协议无法避免这一点。因此,您最好的做法是将迭代器 collect() 到一个向量中,并从中创建一个引用迭代器(就像您使用 solid 一样)。不幸的是,这意味着失去了惰性。
还有一种名为流迭代器的替代迭代器抽象,可以支持这种情况。对于流迭代器,消费者只能保留引用,直到获得下一个引用。虽然我不知道有哪些箱子实现了这个功能,但这将是一个完全不同的特征,没有任何使用 std::iter::Iterator 的函数支持它。在许多情况下,甚至无法使用流迭代器,因为算法需要自由引用多个值。

据我所知,已经有一些尝试实现它的方法了,但是我认为,在 Rust 的当前状态下,你真的不能实现许多并行 Iterator 方法。 - Shepmaster
你的备用流程可行;然而,由于我们没有尾递归优化和因此对不可变值的递归函数,我们仍然需要使用可变性,但这将是一种不同类型的局部可变性,而不是全局可变性。问题在于,虽然我们可以有迭代流,但我们无法将其与记忆化相结合,因为这将需要两个引用指向同一物体:存储在内存中的流的起始点和正在(惰性地)评估的流的当前点。似乎在Rust中无法完成这项任务而不进行重大更改。 - GordonBGood
https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md 是一份有趣的RFC文档,具体讨论了流式迭代器作为该RFC使能的一个功能。 - James Moore

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