Inside for loop variable moved due to use in closure which does not implement the `Copy` trait

I have seen this error in other examples and it being solved by setting a local variable or referencing it. In this case I am inside a for each loop and neither worked.

I am using the mustache library and thought I would try the Map Builder. All of the examples seem to be hardcoded values and all setup in a single line.

pub fn wrapper(template: String, params: RHash) -> Result<String, Error> {
    let data = MapBuilder::new();

    params.foreach(|key: Symbol, value: Value| {
        data.insert_str(key.to_string(), value.to_string());

        return Ok(ForEach::Continue);
    })?;

    return renderer::render(template, data.build())
        .map_err(|e| Error::new(runtime_error(), e.to_string()));
}

I am getting this error:

variable moved due to use in closure

cannot move out of `data`, a captured variable in an `FnMut` closure
move occurs because `data` has type `MapBuilder`, which does not implement the `Copy` trait

Any help on this is appreciated.

You should ask your IDE, or cargo on the terminal, to give you the complete error message, and then share that instead of those snippets.

Also make sure to mention all relevant dependencies used here (as long as they are public), to spare us some detective work if someone wanted to reproduce the issue.

2 Likes

MapBuilder::insert_str takes the builder (self) by value.
So calling this method from the closure passed to foreach requires that this closure to capture data by value.

2 Likes

It's probably best to just make a HashMap directly and then put it in Data::Map after building it. Basically the insert_str function but inlined.

1 Like

I see… insert_str is a sort of Self -> Self method. In order to call that in an FnMut context, you can wrap the value in an Option and use Option::take.

pub fn wrapper(template: String, params: RHash) -> Result<String, Error> {
-   let data = MapBuilder::new();
+   let mut data = Some(MapBuilder::new());

    params.foreach(|key: Symbol, value: Value| {
-       data.insert_str(key.to_string(), value.to_string());
+       data = Some(data.take().unwrap().insert_str(key.to_string(), value.to_string()));

        return Ok(ForEach::Continue);
    })?;

-   return renderer::render(template, data.build())
+   return renderer::render(template, data.unwrap().build())
        .map_err(|e| Error::new(runtime_error(), e.to_string()));
}
1 Like

There are a lot of suggestions on this. This worked. Thank you for this as I wouldn't have worked out to wrap with "some". I tried the mut but that didn't work on it's own. All of it together allows the Map Builder to be used.

You don't need the Option, just data = data.insert_str(...) is enough to fix it, well the let mut data change is also needed.

No, that won’t work in an FnMut; you cannot move out of the variable data there, which is captured by mutable reference; this does not work even if you’re intending to move back in a new value later. (This restriction exists for sound handling of panics.) The only thing you can do is put back a new value immediately, e.g. with std::mem::replace.

1 Like

I confirmed that since I wasn't sure myself. I also looked for a way to get an iterator from RHash, but there is no way. If there were, fold could be used or a regular for loop.

Although you can get a HashMap or a Vec as @drewtato suggested.

1 Like

I just look at the source for RHash and the iteration is done though FFI so a normal Rust iterator is impossible. A fold is possible but would just be sugar on @steffahn Option version or an unsafe version of it.
Also it's using an extern "C" to wrap the closure so panics inside are either UB or an abort depending on the version of the compiler.

2 Likes

No UB; it’s using catch_unwind in the wrapper around the closure, then it turns the panic into a ruby exception.