I have a Vec<(String, i64)>
and need to iterate over the String
s and move them and then iterate over the i64
s.
However, if I move the String
s 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 String
s and i64
s 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 String
s and i64
s separately.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With