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.