0

While implementing an encoding trait for bendy I tried the following:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Copy, Default, Clone)]
pub struct MyFloot {
    pub density: f32,
}

use bendy::encoding::{Error, ToBencode};

impl ToBencode for MyFloot {
    const MAX_DEPTH: usize = 1;

    fn encode(&self, encoder: bendy::encoding::SingleItemEncoder) -> Result<(), Error> {
        self.density
            .to_be_bytes()
            .iter()
            .map(|b| encoder.emit_int(*b));
        Ok(())
    }
}

For which the following error is produced:

error[E0507]: cannot move out of `encoder`, a captured variable in an `FnMut` closure
   --> src\main.rs:17:22
    |
13  |     fn encode(&self, encoder: bendy::encoding::SingleItemEncoder) -> Result<(), Error> {
    |                      ------- captured outer variable
...
17  |             .map(|b| encoder.emit_int(*b));
    |                  --- ^^^^^^^ ------------ `encoder` moved due to this method call
    |                  |   |
    |                  |   move occurs because `encoder` has type `SingleItemEncoder<'_>`, which does not implement the `Copy` trait
    |                  captured by this `FnMut` closure
    |
note: this function takes ownership of the receiver `self`, which moves `encoder`
   --> .cargo\registry\src\github.com-1ecc6299db9ec823\bendy-0.3.3\src\encoding\encoder.rs:257:42
    |
257 |     pub fn emit_int<T: PrintableInteger>(self, value: T) -> Result<(), Error> {
    |                                          ^^^^

But I cannot wrap my head around the move semantics with this..

  • Why would the closure take ownership of encoder here, despite the ommited move ? ( I assume it does )
  • Why does it need to move it out of context?
  • What does that even mean?
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46

4 Answers4

1

I got it!

Firstly, it was not the closure that consumed encoder, it was the emit* call. The first loop iteration would have consumed it, so that's why the compiler was throwing an error.

Secondly, encoder in bendy has the emit_bytes function in trait, which can be used here:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Copy, Default, Clone)]
pub struct MyFloot {
    pub density: f32,
}

use bendy::encoding::{Error, ToBencode};

impl ToBencode for MyFloot {
    const MAX_DEPTH: usize = 1;

    fn encode(&self, encoder: bendy::encoding::SingleItemEncoder) -> Result<(), Error> {
        encodert.emit_bytes(self.density.to_be_bytes());
        Ok(())
    }
}
Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46
  • yeah that is your problem `emit_int` takes `self` instead `&self`, so after the first endian the encoder is dropped – al3x Apr 05 '23 at 07:31
1

This closure takes ownership of encoder because it calls emit_int, which needs ownership of encoder. map requires a closure that can be run an arbitrary number of times, which is why it takes FnMut instead of FnOnce.


Note that Iterator::map is lazy, so if this compiled (say, with Encoder), it wouldn't actually do anything. You would either call Iterator::for_each:

self.density
    .to_be_bytes()
    .iter()
    .for_each(|b| encoder.emit_int(*b));

Or call array::map:

self.density
    .to_be_bytes()
    .map(|b| encoder.emit_int(b));

Or just use a for loop:

for b in self.density.to_be_bytes() {
    encoder.emit_int(b)
}
drewtato
  • 6,783
  • 1
  • 12
  • 17
0

Why would the closure take ownership of encoder here, despite the ommited move?

Because the emit_int method receives self by value:

pub fn emit_int<T: PrintableInteger>(self, value: T) -> Result<(), Error>

So the value must be moved into the closure for this method to be invoked.

move on a closure forces the compiler to move referenced variables into the closure, but the absence of move does not forbid the compiler from moving referenced variables into the closure. It will still move variables in if they are used in the closure within a context that requires they be moved.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
0

https://docs.rs/bendy/0.3.3/bendy/encoding/struct.SingleItemEncoder.html#method.emit_int

At the provided link you can check the documentation of the bendy crate and you will find that the emit_init method implemented on the bendy::encoding::SingleItemEncoder struct takes self as the reciever, meaning that the function takes ownership of the instance of the SingleItemEncoder it is called on as a method. In your case, the parameter encoder is such an instance, so when you call the emit_init method on it at line 17, ownership of the variable is taken. Thus, rust infers that the closure needs to take ownership of this variable to facilitate this method call, meaning that a move of the variable occurs without having to use the 'move' keyword which forces a move in cases where a move is not necessary.

To understand the concept of a move, please refer to The Book: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#variables-and-data-interacting-with-move

I hope that was helpful