Moving a field in elements of a vector that is owned by callee

How does one move (instead of clone) one field in each element of a vector to a new vector ?
Here is an example of what I am trying to do:

// StringInfo
#[derive(Debug, Clone)]
struct StringInfo {
    index : usize  ,
    value : String ,
}
//
// This method clones strings that will be dropped when it returns.
fn vec_string_info_clone( vec_string : Vec<String> ) -> Vec<StringInfo>
{   let mut vec_string_info : Vec<StringInfo> = Vec::new();
    for i in 0 .. vec_string.len() {
        vec_string_info.push(  StringInfo{ index : i, value : vec_string[i].clone() } );
    }
    vec_string_info
}
//
// This method does not clone the strings but 
// it returns the vector in the reverse of the desired order.
fn vec_string_info_pop( mut vec_string : Vec<String> ) -> Vec<StringInfo>
{   let mut vec_string_info : Vec<StringInfo> = Vec::new();
    let len = vec_string.len();
    for i in 0 .. len {
        let string = vec_string.pop().unwrap();
        vec_string_info.push(  StringInfo{ index : len - i - 1, value : string }  );
    }
    vec_string_info
}
fn main() {
    //
    let vec_string        = vec![ "zero".to_string(), "one".to_string() ];
    let vec_string_info   = vec_string_info_clone( vec_string );
    println!( "{:?}", vec_string_info );
    //
    let vec_string        = vec![ "zero".to_string(), "one".to_string() ];
    let vec_string_info   = vec_string_info_pop( vec_string );
    println!( "{:?}", vec_string_info );
}

Indexing with [i] is generally inefficient and inflexible in Rust. Constructing by push() isn't great either if you don't reserve capacity ahead of time.

You can use vec.into_iter().map(…).collect() to transform content of a Vec without cloning the contents. It can optimize very well, and sometimes even reuse Vec's allocation.

Other useful functions are .partition() instead of collect() to split non-contiguous elements into two vecs.

You can't leave holes when moving elements out of a Vec. If you have to, then swap_remove() is relatively cheap (but use pop() if you don't need a random order and can take from the end).
std::mem::take() may be useful for taking individual fields if they have a cheap default value.

vec.retain_mut() can be useful to take fields and remove leftover elements cheaply.

3 Likes

In this particular case, I’d just use iterators

fn vec_string_info_iterator(vec_string: Vec<String>) -> Vec<StringInfo> {
    vec_string
        .into_iter()
        .enumerate()
        .map(|(index, value)| StringInfo { index, value })
        .collect()
}

In other situations, for instance for an arbitrary-order access, as long as you have mutable access, you can avoid a clone of a String value by instead replacing the original with an empty String (which in Rust doesn’t require any allocation costs to create).

// minimally changed from your `…_clone` variant
fn vec_string_info_take(mut vec_string: Vec<String>) -> Vec<StringInfo> {
    let mut vec_string_info: Vec<StringInfo> = Vec::new();
    for i in 0..vec_string.len() {
        vec_string_info.push(StringInfo {
            index: i,
            value: std::mem::take(&mut vec_string[i]),
        });
    }
    vec_string_info
}

This uses the mem::take method – the more general procedure of “take ownership by providing replacement value” is also available via mem::replace (and mem::swap), whereas mem::take is a convenience wrapper that chooses the Default::default value for replacement.

If the value in question were an Option, there’s a .take() method as well.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.