Chaining iterators when using impl Iterator


#1

Is there a way to make a non-consuming iterator that I can chain together.

I’ve got a function that consumes an iterator and returns a new iterator.

fn ema<I>(mut i: I, period: usize) -> impl Iterator<Item = f32> + Clone 
where
    I: Iterator<Item = f32> + Clone,
{
...
}

Calling this with

{
    let prices = Vec::with_capacity(10000);
    ema(ema(prices.iter(),10),20);
}

But prices is consumed. So I created a non-consuming version

fn ema<'a,I:'a>(i: &I, period: usize) -> impl Iterator<Item = f32> + Clone +'a 
where
    I: Iterator<Item = &'a f32> + Clone,
{
...
}

But I can’t figure out how to chain them, as I get an error:-

error[E0271]: type mismatch resolving `<impl std::clone::Clone+std::iter::Iterator as std::iter::Iterator>::Item == &f32`
   --> driver\src\technical_analysis.rs:215:25
    |
215 |         let double_ema = ema(&ema(prices.iter().by_ref(), 20), 15);
    |                         ^^^ expected f32, found &f32
    |
    = note: expected type `f32`
               found type `&f32`

I can fix it by creating an intermediate vec but that seems wasteful allocation

let ema1 = ema(prices.iter().by_ref(),15).collect::<Vec<_>>();
let ema2 = ema(ema1.iter().by_ref(),15).collect::<Vec<_>>();

Is there an idiom I’m missing?


#2

prices isn’t consumed with iter(), but it does give an iterator that yields references. However, you can call prices.iter().cloned() to get an iterator that yields values.


#3

So the mut approach is the correct idiom


#4

It’s a fine approach for cheap Clone types. If you wanted references, you can do:

fn ema<'a, I>(mut i: I, period: usize) -> impl Iterator<Item = &'a f32>
where
    I: Iterator<Item = &'a f32>,

You don’t need the cloned() call for this variant but now you’re yielding refs as well. There’re ways to abstract this as well but I’d just stick to cloned() here.


#5

I tried to follow this, but I get an (expected) lifetime error

fn ema<'a, I: 'a>(mut i: I, period: usize) -> impl Iterator<Item = &'a f32> + Clone + 'a
where
    I: Iterator<Item = &'a f32> + Clone,
{
    let alpha = (2 / (period + 1)) as f32;
    let ema0 = i.by_ref().take(1).next().unwrap();
    i.scan(ema0, move |ema_prev, price| {
        *ema_prev = &(*ema_prev + alpha * (price - *ema_prev));
        Some(*ema_prev)
    })
}

error[E0597]: borrowed value does not live long enough
   --> src\technical_analysis.rs:102:22
    |
102 |         *ema_prev = &(*ema_prev + alpha * (price - *ema_prev));
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- temporary value only lives until here
    |                      |
    |                      temporary value does not live long enough
    |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 95:8...

I think I’m missing something? To add, the value approach does work, but will become expensive once I have larger objects


#6

You can abstract over owned vs borrowed, eg like this


#7

Thank you, that does work.

This seems to be saying that I can be prices asf32 or as &f32, and we abstract over either. Fair enough. What I return is a f32 that could be borrowed if chained to another function, though in this instance, returns an iterator yielding values in this case.

I didn’t even know about std::Borrow before now.