为什么使用 by_ref 不能防止 .all 消耗迭代器?

7
以下输出预期的结果:9,然后true
fn main() {
    let mut a = (1..10);
    println!("{}", a.by_ref().count()); // 9
    println!("{}", a.all(|x| x < 10)); // true
}

以下代码会依次输出true0!请点击此处进行尝试。
fn main() {
    let mut a = (1..10);
    println!("{}", a.by_ref().all(|x| x < 10)); // true
    println!("{}", a.count()); // 0
}
Playground
在调用 .all 之前对迭代器使用 .by_ref() 是否会防止 .all 消耗迭代器?
3个回答

8
其他答案已经涵盖了实现方面的内容,但我想解释一下术语,因为我认为这是混淆的根源:
在Rust中,consume具有与所有权相关的狭义定义——“消费”值的函数从调用者那里取得该值的所有权。由于函数可以销毁该值或将其传递给其他地方,因此该值无法再被原始所有者使用。
对于实现Iterator的值,在消耗(use up)它们的方法(例如count()sum())通常也会排空(exhaust)它们。 其他方法,例如map()enumerate(),消耗迭代器并返回一个新的迭代器,该迭代器将排空提供的原始迭代器,只要自身已经排空。但是,还有像take()scan这样的方法,即使返回的迭代器完全排空,它们仍然不一定排空迭代器。 使用这种方法时,by_ref()很方便,可以使原始迭代器可用于进一步处理。
let mut iter = 1..1000;
let sum_first_50 = iter.by_ref().take(50).sum();
let sum_second_50 = iter.by_ref().take(50).sum();
// ... iter available to produce the remaining 899 numbers ...

如果第一行没有by_ref(),第二行将无法编译,因为第一个take()会消耗(但不耗尽)iter。使用by_ref()iter既不会被消耗(因为by_ref()保护它),也不会被耗尽(因为我们将其与take()组合)。将by_ref()与既消耗又耗尽迭代器的方法(例如count())结合使用是明确定义的,但是毫无用处,因为它只会让您拥有一个耗尽的迭代器。

如果您想避免迭代器被耗尽(或以其他方式被改变),则可以使用clone()而不是by_ref()

fn main() {
    let a = 1..10;
    println!("{}", a.clone().all(|x| x < 10)); // true
    println!("{}", a.count()); // 9
}

3

在迭代器上调用all()count()会多次调用next()。即使迭代器没有被消耗(moved-from),由于对next()的重复调用,其内部状态已经改变。

all()true结束时,已经到达了序列的末尾,因此count()仍然是可调用的,但产生的值为0

在第一种情况下,count()到达了序列的末尾,但是all()的文档指出,在一个空序列中将返回true


啊,这很有帮助。所以在第一个例子中,all()甚至没有遍历元素,因为它已经耗尽了,但它仍然返回true。非常感谢您的帮助。 - Jordan

3
每个迭代器适配器(如.take.filter,以及当然是.all)仅通过.next函数访问迭代器的元素。 .by_ref唯一的作用是允许你在使用迭代器适配器后继续使用迭代器。任何消耗过的元素仍将被消耗。如果不想这样做,应该克隆迭代器。
这在Iterator::by_ref文档中有解释:
let a = [1, 2, 3];

let iter = a.iter();

let sum: i32 = iter.take(5).fold(0, |acc, i| acc + i);

assert_eq!(sum, 6);

// if we try to use iter again, it won't work. The following line
// gives "error: use of moved value: `iter`
// assert_eq!(iter.next(), None);

// let's try that again
let a = [1, 2, 3];

let mut iter = a.iter();

// instead, we add in a .by_ref()
let sum: i32 = iter.by_ref().take(2).fold(0, |acc, i| acc + i);

assert_eq!(sum, 3);

// now this is just fine:
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);

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