Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to repeat a clone operation except for the last item in a Rust iterator?

Tags:

iterator

rust

I often see the pattern where I need to loop over a range and call a function that takes ownership of its argument. Since that function takes ownership of its argument, I must clone the value. For example:

let a = "hello".to_string();
for i in 0..10 {
    print_me(a.clone());
}

fn print_me(s: String) {
    println!("{}", s);
}

I would like to find a pattern where the value is cloned for all calls except the last one as to prevent a redundant clone.

The simplest approach is to add an explicit check on the loop index:

for i in 0..10 {
    if i == 9 {
        print_me(a);
    } else {
        print_me(a.clone());
    }
}

But the compiler is not able understand the pattern and complains about the moved value during iteration.

I also tried to build an iterator that does the clones and move using repeat_with(), chain() and once() but the closure cosumes the value:

let iter = std::iter::repeat_with(|| a.clone())
            .take(9)
            .chain(std::iter::once(a))

What would be the easiest way to achieve this pattern?

Thanks!

like image 952
big_gie Avatar asked Dec 22 '22 15:12

big_gie


2 Answers

As an alternative solution, you can make the compiler understand that it is in fact the last iteration by breaking out of the loop:

for i in 0..10 {
    if i == 9 {
        print_me(a);
        break;
    } else {
        print_me(a.clone());
    }
}
like image 104
ollpu Avatar answered Dec 26 '22 01:12

ollpu


itertools::repeat_n does exactly this:

#[derive(Clone, Debug)]
pub struct RepeatN<A> {
    elt: Option<A>,
    n: usize,
}

/// Create an iterator that produces `n` repetitions of `element`.
pub fn repeat_n<A: Clone>(element: A, n: usize) -> RepeatN<A> {
    if n == 0 {
        RepeatN { elt: None, n, }
    } else {
        RepeatN { elt: Some(element), n, }
    }
}

impl<A: Clone> Iterator for RepeatN<A> {
    type Item = A;

    fn next(&mut self) -> Option<Self::Item> {
        if self.n > 1 {
            self.n -= 1;
            self.elt.as_ref().cloned()
        } else {
            self.n = 0;
            self.elt.take()
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.n, Some(self.n))
    }
}

Using unsafe code you could make a (slightly) more efficient implementation however.

like image 43
orlp Avatar answered Dec 26 '22 00:12

orlp