Partial move of Vec of tuple

Viewed 553

I have a Vec<(String, i64)> and need to iterate over the Strings and move them and then iterate over the i64s.

However, if I move the Strings I have to store the i64 again into another Vec:

let l: Vec<_> = l
    .into_iter()
    .map(|(string, int)| {
        drop(string);
        int
    })
    .collect();
                           
for i in l {
    process(i);
}

How can I iterate over the Strings and i64s separately without incurring any additional performance overhead.

The only solution I can think of at the moment that will not cause additional operations is to store the Strings and i64s separately.

3 Answers

You can use std::mem::take() while iterating over the Vec in a first pass to take ownership of the String element while putting a non-allocating Default in its place. This allows you to keep the Vec in its original form, so no extra container is required.

fn foo(mut inp: Vec<(String, i64)>) {
    // First pass over the Vec "extracts" the owned Strings, replacing the content
    // in the Vec by a non-allocating empty String, which is close to zero cost;
    // this leaves the Vec as is, so no intermediate representation is needed.
    for s in inp.iter_mut().map(|(s, _)| std::mem::take(s)) {
        // Process String
    }

    // Something happens

    // Second pass ignores the empty strings, processes the integers
    for i in inp.into_iter().map(|(_, i)| i) {
        // Process the integers
    }
}

If the type of the list can be changed to Vec<Option<String>, i64> from Vec<String, i64>, then you can try the following way.

fn main() {
    let mut l = Vec::new();
    l.push((Some("a".to_string()), 1i64));
    l.push((Some("b".to_string()), 2));
    l.push((Some("c".to_string()), 3));
    l.push((Some("d".to_string()), 4));
    
    l.iter_mut().for_each(|(s, _)| {
        if let Some(x) = s.take() { 
            println!("Processing string: {}", x);
        }
    });

    l.iter().for_each(|(_, i)| {
        println!("Processing int: {}", i);
    });
}

Playground

Use unzip to separate them:

fn main(){
    let original = vec![("a", 1), ("b", 2)];
    let (s, i): (Vec<_>, Vec<_>) = original.into_iter().unzip();
                           
    for a in s {
        println!("{}", a);
    }
    
    for b in i {
        println!("{}", b);
    }
}

Playground

Related