I'm experimenting with the Fuse
iterator adapter and am getting unexpected results (Playground link):
fn main() {
let mut i1 = (1..3).scan(1, |_, x| {
if x < 2 { None } else { Some(x) }
});
println!("{:?}", i1.next());
println!("{:?}", i1.next());
println!("{:?}", i1.next());
println!("");
let mut i2 = (1..3).scan(1, |_, x| {
if x < 2 { None } else { Some(x) }
}).fuse();
println!("{:?}", i2.next());
println!("{:?}", i2.next()); // This should print None
println!("{:?}", i2.next());
println!("");
}
Which prints:
None
Some(2)
None
None
Some(2)
None
Iterator i1
is returning what I expect. It returns None
, then Some(2)
, then None
. i2
is the same iterator adapted with fuse()
. Fuse should make it return None
after the first None
, and since the first value it returns is None
that should be the only value it returns. However, it behaves the same as i1
. What am I doing wrong?
An iterator helps to iterate over a collection of values such as arrays, vectors, maps, etc. Iterators implement the Iterator trait that is defined in the Rust standard library. The iter() method returns an iterator object of the collection. Values in an iterator object are called items.
To fix this warning and consume the iterator, we'll use the collect method, which we used in Chapter 12 with env::args in Listing 12-1. This method consumes the iterator and collects the resulting values into a collection data type.
In Rust programs, iterators are often used to modify or generate data. But to have a more usable collection, like a Vec or String, we must convert the iterator. With collect, we have a powerful built-in function that converts an iterator into a type like a Vec or String. Collect can perform many conversions.
TL;DR Summary: This was a bug and is fixed in Rust 1.19 and newer.
I'm pretty sure you are doing nothing wrong. This appears to be either a bug (my guess) or a very confusing interaction. Check out this expanded example:
#![feature(fused)]
fn dump<I: Iterator<Item = i32>>(label: &str, mut iter: I) {
println!("= Running: {}", label);
for _ in 0..10 {
println!("{:?}", iter.next());
}
println!("");
}
fn boxed_internal_fuse() -> Box<Iterator<Item = i32>> {
Box::new((1..3)
.scan(1, |_, x| if x < 2 { None } else { Some(x) })
.fuse())
}
fn boxed_no_fuse() -> Box<Iterator<Item = i32>> {
Box::new((1..3)
.scan(1, |_, x| if x < 2 { None } else { Some(x) }))
}
use std::iter::FusedIterator;
fn boxed_no_fuse_but_fused() -> Box<FusedIterator<Item = i32>> {
Box::new((1..3)
.scan(1, |_, x| if x < 2 { None } else { Some(x) }))
}
fn main() {
let i1 = (1..3)
.scan(1, |_, x| if x < 2 { None } else { Some(x) });
dump("Scan", i1);
let i2 = (1..3)
.scan(1, |_, x| if x < 2 { None } else { Some(x) })
.fuse();
dump("Fuse<Scan>", i2);
dump("Box<Fuse<Scan>>", boxed_internal_fuse());
dump("Fuse<Box<Iterator>>", boxed_no_fuse().fuse()); // All `None`s
dump("Fuse<Box<FusedIterator>>", boxed_no_fuse_but_fused().fuse());
}
The trick is that FusedIterator
is a unstable trait that is aimed at improving the efficiency. It lets Iterator::fuse
know that it's a no-op.
However, in this case, the conditions are necessary but not sufficient:
impl<B, I, St, F> FusedIterator for Scan<I, St, F>
where I: FusedIterator, F: FnMut(&mut St, I::Item) -> Option<B> {}
It's true that if the underlying iterator is FusedIterator
and starts returning None
, scan
will keep returning None
. However, that's not the only way to get None
— the closure can also return None
!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With