5

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?

St.Antario
  • 26,175
  • 41
  • 130
  • 318

3 Answers3

8

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.

trent
  • 25,033
  • 7
  • 51
  • 90
4

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(),
            }
prog-fh
  • 13,492
  • 1
  • 15
  • 30
2
#[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>>>()
}
Andrea Nardi
  • 413
  • 4
  • 11
  • 2
    `name` and `id` are cloned twice. The first clone could be avoided with an immutable borrow. – prog-fh Sep 18 '20 at 21:22
  • You cannot borrow `name` and `id` and use them after `into_iter` is called. `values_info` at that point is consumed. My approach look very similar to yours. The amount of `clone` calls is the same and there is no implicit copy. – Andrea Nardi Sep 18 '20 at 21:27
  • Good catch @trentcl! I should not be saying "derive Copy instead" – Andrea Nardi Sep 18 '20 at 21:31
  • 1
    You are correct that this answer has the same number of clones, but only because @prog-fh's solution also does two more than necessary! `let name = &values_info.name;` would work as well. Cloning outside the closure is a technique generally useful for `move` closures to eliminate references to the environment; you don't need to do that here. – trent Sep 18 '20 at 21:36
  • 1
    you are both right, my first clone was the remaining of a first attempt, I thought I had removed it at the beginning, and borrowing this clone was obviously a total non-sense. Sorry for the confusion. – prog-fh Sep 18 '20 at 21:40