St.Antario
St.Antario

Reputation: 27425

How to eliminate partial move when calling map on an iterator

I have a simple (I thought it should be) task to map values contained in a Vec and produce another Vec:

#[derive(Clone)]
struct Value(u32);
#[derive(Clone)]
struct Id(u32);

struct ValuesInfo {
    values: Vec<Value>,
    name: String,
    id: Id
}

struct ValueInfo{
    value: Value,
    name: String,
    id: Id
}

fn extend_values(v: Vec<ValuesInfo>) -> Vec<Vec<ValueInfo>> {
    v.into_iter().map(|values_info|{
        values_info.values.into_iter().map(|value|{
            ValueInfo{
                value,
                name: values_info.name.clone(),
                id: values_info.id.clone()
            }
        }).collect::<Vec<ValueInfo>>()
    }).collect::<Vec<Vec<ValueInfo>>>()
}

Playground permalink

Here I have a partial move error looking as

   Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `values_info`
   --> src/lib.rs:20:44
    |
20  |         values_info.values.into_iter().map(|value|{
    |                            -----------     ^^^^^^^ value borrowed here after partial move
    |                            |
    |                            `values_info.values` moved due to this method call
...
23  |                 name: values_info.name.clone(),
    |                       ----------- borrow occurs due to use in closure
    |
note: this function consumes the receiver `self` by taking ownership of it, which moves `values_info.values`
    = note: move occurs because `values_info.values` has type `std::vec::Vec<Value>`, which does not implement the `Copy` trait

error: aborting due to previous error

I need this partial move because this is what the task is about. Is there any workaround to solve the error?

Upvotes: 5

Views: 3150

Answers (3)

trent
trent

Reputation: 28005

In the 2018 edition of Rust, closures always capture entire variables by name. So the closure passed to the inner map would take a reference to values_info, which is not valid because values_info has already been partially moved (even though the closure does not need access to the part that was moved).

Rust 2021+

Since Rust 2021, captures borrow (or move) only the minimal set of fields required in the body of the closure. This makes the original code work as you expected¹, so you can make the error go away by simply changing the playground edition to 2021. See the edition guide for more.

Rust 2015 & 2018

In previous editions, you can do it manually: destructure the ValuesInfo first, and only capture name and id inside the closure. You can destructure the ValuesInfo as soon as you get it, in the argument list of the outer closure:

fn extend_values(v: Vec<ValuesInfo>) -> Vec<Vec<ValueInfo>> {
    v.into_iter()
        .map(|ValuesInfo { values, name, id }| {
        //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ like `let ValuesInfo { values, name, id } = values_info;`
            values
                .into_iter()
                .map(|value| ValueInfo {
                    value,
                    name: name.clone(),
                    id: id.clone(),
                })
                .collect()
        })
        .collect()
}

See also


¹ Unless ValuesInfo implements Drop, which makes any destructuring or partial move unsound.

Upvotes: 8

prog-fh
prog-fh

Reputation: 16925

Naming values_info in the closure will borrow it as a whole although it is already partially moved (as told by the error message). You should borrow the members you need beforehand.

        let name=&values_info.name;
        let id=&values_info.id;
        values_info.values.into_iter().map(|value|{
            ValueInfo{
                value,
                name: name.clone(),
                id: id.clone(),
            }

Upvotes: 4

Andrea Nardi
Andrea Nardi

Reputation: 423

#[derive(Clone)] //you could derive Copy. u32 is generally cheap to copy
struct Value(u32);
#[derive(Clone)] //you could derive Copy
struct Id(u32);

struct ValuesInfo {
    values: Vec<Value>,
    name: String,
    id: Id
}

struct ValueInfo{
    value: Value,
    name: String,
    id: Id
}

//No need to consume v. Not sure if it will come in handy
fn extend_values(v: &Vec<ValuesInfo>) -> Vec<Vec<ValueInfo>> {

    //Use into_iter only if you need the ownership of the content of the array
    // which I don't think you need because you are cloning every value of ValueInfo
    //except for value which is already cheep to copy/clone
    v.iter().map(|values_info|{
        values_info.values.iter().map(|value|{
            ValueInfo{
                value: value.clone(),
                name: values_info.name.clone(),
                id: values_info.id.clone()
            }
        }).collect::<Vec<ValueInfo>>()
    }).collect::<Vec<Vec<ValueInfo>>>()
}

That said, if you really don't want to copy/clone Value you need to clone name and id before hand. The compiler stops you from using values_info.name.clone() because the function into_iter already consumed values_info. The code would look something like this if you really don't want to copy Value

#[derive(Clone)]
struct Value(u32);
#[derive(Clone)]
struct Id(u32);

struct ValuesInfo {
    values: Vec<Value>,
    name: String,
    id: Id
}

struct ValueInfo{
    value: Value,
    name: String,
    id: Id
}

fn extend_values(v: Vec<ValuesInfo>) -> Vec<Vec<ValueInfo>> {
    v.into_iter().map(|values_info|{
        let name = values_info.name.clone();
        let id = values_info.id.clone();
        values_info.values.into_iter().map(
        |value| {
            ValueInfo{
                value,
                name: name.clone(),
                id: id.clone(), 
            }
        }).collect::<Vec<ValueInfo>>()
    }).collect::<Vec<Vec<ValueInfo>>>()
}

Upvotes: 2

Related Questions