dimo414
dimo414

Reputation: 48874

What type signature to use for an iterator generated from a slice?

I have this toy example, but it's what I'm trying to accomplish:

fn lazy_vec() {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(vec.into_iter());
    iter = Box::new(iter.map(|x| x + 1));
    // potentially do additional similar transformations to iter
    println!("{:?}", iter.collect::<Vec<_>>());
}

This (if I'm not mistaken) is a lazy iterator pattern, and the actual map operation doesn't occur until .collect() is called. I want to do the same thing with slices:

fn lazy_slice() {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let slice: &[i64] = &vec[..3];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(slice.into_iter());
    iter = Box::new(iter.map(|x| x + 1));
    // potentially do additional similar transformations to iter
    println!("{:?}", iter.collect::<Vec<_>>());
}

This results in a type mismatch:

error[E0271]: type mismatch resolving `<std::slice::Iter<'_, i64> as std::iter::Iterator>::Item == i64`
 --> src/main.rs:4:47
  |
4 |     let mut iter: Box<Iterator<Item = i64>> = Box::new(slice.into_iter());
  |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found i64
  |
  = note: expected type `&i64`
             found type `i64`
  = note: required for the cast to the object type `std::iter::Iterator<Item=i64>`

I can't figure out what I need to do to resolve this error. The second note made me think I needed:

iter = Box::new(iter.map(|x| x + 1) as Iterator<Item = i64>);

or

iter = Box::new(iter.map(|x| x + 1)) as Box<Iterator<Item = i64>>;

These fail with other errors depending on the exact syntax (e.g. expected reference, found i64, or expected i64, found &i64). I've tried other ways to declare the types involved, but I'm basically just blindly adding & and * in places and not making any progress.

What am I missing here? What do I need to change in order to make this compile?


Edit

Here's a slightly more concrete example - I need iter to be mut so that I can compose an unknown number of such transformations before actually invoking .collect(). My impression was this was a somewhat common pattern, apologies if that wasn't correct.

fn lazy_vec(n: i64) {
    let vec: Vec<i64> = vec![1, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(vec.into_iter());
    for _ in 0..n {
        iter = Box::new(iter.map(|x| x + 1));
    }
    println!("{:?}", iter.collect::<Vec<_>>());
}

I'm aware I could rewrite this specific task in a simpler way (e.g. a single map that adds n to each element) - it's an oversimplified MCVE of the problem I'm running into. My issue is this works for lazy_vec, but I'm not sure how to do the same with slices.


Edit 2

I'm just learning Rust and some of the nomenclature and concepts are new to me. Here's what I'm envisioning doing in Python, for comparison. My intent is to do the same thing with slices that I can currently do with vectors.

#!/usr/bin/env python3

import itertools

ls = [i for i in range(10)]

def lazy_work(input):
  for i in range(10):
    input = (i + 1 for i in input)
  # at this point no actual work has been done
  return input

print("From list: %s" % list(lazy_work(ls)))
print("From slice: %s" % list(lazy_work(itertools.islice(ls, 5))))

Obviously in Python there's no issues with typing, but hopefully that more clearly demonstrates my intent?

Upvotes: 0

Views: 1005

Answers (1)

Shepmaster
Shepmaster

Reputation: 432089

As discussed in What is the difference between iter and into_iter?, these methods create iterators which yield different types when called on a Vec compared to a slice.

[T]::iter and [T]::into_iter both return an iterator which yields values of type &T. That means that the returned value doesn't implement Iterator<Item = i64> but instead Iterator<Item = &i64>, as the error message states.

However, your subsequent map statements change the type of the iterator's item to an i64, which means the type of the iterator would also need to change. As an analogy, you've essentially attempted this:

let mut a: &i64 = &42;
a = 99;

Iterator::cloned exists to make clones of the iterated value. In this case, it converts a &i64 to an i64 essentially dereferencing the value:

fn lazy_slice(n: i64) {
    let array = [1i64, 2, 3, 4, 5];
    let mut iter: Box<Iterator<Item = i64>> = Box::new(array.iter().cloned());
    for _ in 0..n {
        iter = Box::new(iter.map(|x| x + 1));
    }
    println!("{:?}", iter.collect::<Vec<_>>());
}

Upvotes: 2

Related Questions