How to create N-1 clones of an object in a loop and consume the original object in the *last* iteration?

I have a loop that looks like this:

let (channel_tx, channel_rx) = create_channel();
let mut listeners = VecDeque::new();

for settings in config.interfaces.iter() {
    listeners.push_back(MyListener::new(settings, channels_tx.clone());
}

mem::drop(channels_tx);

/* and so on...*/

Now I think it is unnecessary wasteful to create a separate clone of channel_tx for each MyListener instance and drop the original channel_tx object afterwards. I'd rather create a clone of channel_tx for each MyListener instance except for the last one, and make the last one consume the original channel_tx object. What is an elegant way to do this?

I think about something like this:

for settings in config.interfaces.iter() {
    let my_channel = if IS_LAST_ONE { channels_tx } else { channels_tx.clone() };
    listeners.push_back(MyListener::new(settings, my_channel);
}

But how to "detect" the last iteration of the loop?

And how to get around error:

value moved here, in previous iteration of loop

Thanks!

Rewrite your loop to skip either the first or last iteration and handle that separately.

This is only possible if the llength of the iterable is known. But this seems to be the case for you. so you can use repeat_n in std::iter - Rust

Something like this

listeners.extend(
    config.interfaces.iter().zip(
        std::iter::repeat_n(channels_tx, config.interfaces.len())
    ).map(|(settings, my_channel )| MyListener::new(settings, my_channel))
);
2 Likes

Yeah, I need to handle the last iteration differently.

But how to "detect" the last iteration? The iterator has no is_last() method :cry:

I could use a separate counter, but that seems a bit awkward...

Does std::iter::repeat_n create N-1 clones and return the originak object as its last element?

If so, this would be perfect :grinning:

You can write a utility for this based on std::iter::Peekable:

use std::iter::Peekable;

pub struct ZipCloned<I:Iterator,T:Clone> {
    iter: Peekable<I>,
    val: Option<T>,
}

impl<I:Iterator,T:Clone> Iterator for ZipCloned<I,T> {
    type Item = (I::Item, T);
    fn next(&mut self)->Option<(I::Item, T)> {
        let a = self.iter.next()?;
        let b = match self.iter.peek() {
            None => self.val.take(),
            Some(_) => self.val.clone(),
        }?;
        Some((a,b))
    }
}

pub fn zip_cloned<I: Iterator, T:Clone>(iter: I, val:T)->ZipCloned<I,T> {
    ZipCloned {
        iter: iter.peekable(),
        val: Some(val),
    }
}


/* ------ Usage -----

    let (channel_tx, channel_rx) = create_channel();
    let mut listeners = VecDeque::new();

    for (settings, tx) in zip_cloned(config.interfaces.iter(), channel_tx) {
        listeners.push_back(MyListener::new(settings, tx);
    }
    
*/
1 Like

From the linked doc

This is very similar to using repeat() with Iterator::take(), but repeat_n() can return the original value, rather than always cloning.

Does "can" mean it actually does in my use case? :thinking:

Let's have a look at the implementation: repeat_n.rs - source

1 Like

Looks good! :+1:

1 Like

This is a brave hypothesis... :wink:

You don't, but defer the previous clone:

let (channel_tx, channel_rx) = create_channel();
let mut listeners = VecDeque::new();

let mut iter = config.interfaces.iter().fuse();
let mut prev_settings = iter.next();

for next_settings in iter  {
    let settings = prev_settings.take().unwrap(); // Guaranteed to be Some(...)
    listeners.push_back(MyListener::new(settings, channels_tx.clone());
    prev_settings = Some(next_settings);
}

if let Some(settings) = prev_settings { // Maybe None, when there wasn't any interface
   listeners.push_back(MyListener::new(settings, channels_tx);
}
1 Like

Interesenting. But I think I'll go with std::iter::repeat_n() :smiling_face:

This is a general technique:

Instead looking into the future (when you cannot), defer the action/decision and first take the step into that future and then look back in the past or act in the past.

1 Like