Rust calling FnMut within implemented Struct

Viewed 206

i have the following code snippet which implements some kind of Emitter Struct:

type Callback<'a> = Option<&'a mut dyn FnMut()>;

struct Emitter<'a> {
  cb: Callback<'a>
}

impl<'a> Emitter<'a> {

  fn emit(&mut self) {
    if self.cb.is_some() {
      let f = self.cb.unwrap();
      f()
    }
  }
}

fn main() {

  let mut cb = || println!("test");

  let mut e = Emitter { 
    cb : Some(&mut cb)
  };
  
  e.emit();
}

The emit() function tries to run the saved callback clojure. But i cannot wrap my head around how to run the callback, since the code produces the following error:

  --> src/main.rs:11:15
   |
11 |       let f = self.cb.unwrap();
   |               ^^^^^^^
   |               |
   |               move occurs because `self.cb` has type `Option<&mut dyn FnMut()>`, which does not implement the `Copy` trait
   |               help: consider borrowing the `Option`'s content: `self.cb.as_ref()`

Appreciate some help :)

Here is the snippet on replit: https://replit.com/@lutzer/RustEmitterTest

1 Answers

What's going on here is that your line

let f = self.cb.unwrap();

would want to move the closure out of the Option enum. This operation consumes that enum, which isn't allowed for things that belong to a struct.

Here is a simpler example to show what I mean:

fn main() {
  let an_option = Some(String::from("Woot!");
  let the_value = an_option.unwrap();
  println!("The value is {}", the_value);

  println!("The option is {:?}", an_option); // error here! Can't use an_option any more!!!

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4a4a3660b68ebada99113db5165b6e76

So if you take ownership of something stored inside the Some part of an Option, via unwrap, then the whole Option gets moved out. You can see that in the signature of unwrap:

pub const fn unwrap(self) -> T

Note how it says self, and not &self or &mut self. That means, after calling unwrap, that the enum gets consumed and cannot be used any more, unless the value inside the Some part can simply be copied (If you replace the String in my example with, say, an integer, it will compile without issue).

The comment by Omer Erden then explains a way around that: Ask the Option to give you a mutable reference instead via as_mut.

Or skip all that directly and use the map method of option, which you can use to do something if the option is Some and just not do anything if it's None.

Related