naumb
naumb

Reputation: 21

Why does Iterator::inspect in Rust behave weirdly when applying take?

I came across the following Rust-snippet (here):

fn main() {
    let foo = (0..100)
    .skip(2)
    .inspect(|x| { dbg!(x); })
    .take(5)
    .collect::<Vec<u32>>();

    dbg!(foo);
}

Upon first sight, I assumed inspect to print all values according to the range (2..100). Further, I assumed the final result in foo to be [2, 3, 4, 5, 6].

While the latter was true, the same numbers were also printed by inspect, missing everyhing in (7..100):

[src/main.rs:4:20] x = 2
[src/main.rs:4:20] x = 3
[src/main.rs:4:20] x = 4
[src/main.rs:4:20] x = 5
[src/main.rs:4:20] x = 6
[src/main.rs:8:5] foo = [
    2,
    3,
    4,
    5,
    6,
]

I thought the purpose of inspect was mainly for debugging and error handling reasons, as stated in the documentation, but I think this behavior is contradictionary to this idea, isn't it?

Further, when changing the code to inspect the iterator right after it was created, the behavior of inspect is even more counterintuitive - starting at 0 but ending at 7:

fn main() {
    let foo = (0..100).into_iter()
    .inspect(|x| { dbg!(x); })
    .skip(2)
    .take(5)
    .collect::<Vec<u32>>();

    dbg!(foo);
}
[src/main.rs:3:20] x = 0
[src/main.rs:3:20] x = 1
[src/main.rs:3:20] x = 2
[src/main.rs:3:20] x = 3
[src/main.rs:3:20] x = 4
[src/main.rs:3:20] x = 5
[src/main.rs:3:20] x = 6
[src/main.rs:8:5] foo = [
    2,
    3,
    4,
    5,
    6,
]

I know that Iterators in Rust are lazy and I've heard that such chains are also optimized by the compiler, but why is this also true when using inspect?

Upvotes: 0

Views: 54

Answers (1)

prog-fh
prog-fh

Reputation: 16910

.take() will consume five elements.
This means that the previous stage will have to emit these five elements, nothing more, in order to satisfy .take().
Inspecting what happens between .skip() and .take(), we will see the five needed elements (if so many can be produced beforehand).

However, .skip() will have to consume three elements in order to be able to emit the first which will be consumed by take(); thus .skip() will have to consume seven elements.
Inspecting what happens before .skip(), we will see the seven needed elements (if so many can be produced beforehand).

The range (0..100) could produce many more elements than the seven that are expected by the following stages, but it does not have to.
This is the purpose of lazy iterators: produce only what is needed by the following stages, only when it is actually needed by the following stages.

Upvotes: 1

Related Questions