Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 982
St.Antario Avatar asked Dec 17 '22 12:12

St.Antario


2 Answers

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

  • How to use struct self in member method closure
  • HashMap borrow issue when trying to implement find or insert is solved using a similar trick.

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

like image 142
trent Avatar answered Jan 24 '23 18:01

trent


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(),
            }
like image 28
prog-fh Avatar answered Jan 24 '23 20:01

prog-fh