Why does taking an owned value by reference turn a closure from FnOnce to FnMut

Here is an abridged version of a structure and it's implementation:

use {
    super::{load_from_path, Config},
    gtk::glib,
    notify::{recommended_watcher, RecommendedWatcher, RecursiveMode, Watcher},
    std::{cell::OnceCell, io, path::PathBuf, result},
    xdg::BaseDirectories,
};

pub struct Manager {
    xdg: BaseDirectories,
    path: PathBuf,
    watcher: OnceCell<RecommendedWatcher>,
}

impl Manager {
    pub fn new(xdg_prefix: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let xdg = BaseDirectories::with_prefix(xdg_prefix)?;
        let path = xdg
            .find_config_file("config.ron")
            .or(xdg.find_config_file("config.ron"))
            .ok_or(io::Error::new(
                io::ErrorKind::NotFound,
                "Config file not found",
            ))?;

        Ok(Self {
            xdg,
            path,
            watcher: OnceCell::new(),
        })
    }

    pub fn start(&self, watcher_tx: glib::Sender<Config>) -> crate::Result<()> {
        let config_path = self.path.to_owned();
        let mut watcher = recommended_watcher(move |res: Result<notify::event::Event, _>| match res {
            Ok(event) => {
                use notify::event::{
                    AccessKind::*, AccessMode::*, EventKind::*, ModifyKind::*,
                    RenameMode::*,
                };

                if event.kind == Access(Close(Write))
                    || event.kind == Modify(Name(From))
                {
                    if let Ok(config) = load_from_path(config_path) {
                        if let Err(e) = watcher_tx.send(config) {
                            log::error!("{e:?}");
                        }
                    }
                }
            }
            Err(e) => log::error!("{e:?}")
        })?;
        watcher.watch(&self.path, RecursiveMode::NonRecursive)?;

        if let Err(_watcher) = self.watcher.set(watcher) {
            log::error!("Failed to set watcher in OnceCell");
            // add better error here
        }

        Ok(())
}

This does not compile and it gives the following error:

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/config/manager.rs:82:47
   |
82 |         let mut watcher = recommended_watcher(move |res: Result<notify::event::Event, _>| match res {
   |                           ------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
   |                           |
   |                           the requirement to implement `FnMut` derives from here
...
92 |                     if let Ok(config) = load_from_path(config_path) {
   |                                                        ----------- closure is `FnOnce` because it moves the variable `config_path` out of its environment

Okay, so this closure can't be FnMut because it moves this variable out of its environment. Why is this? If the closure now has full ownership, why is this causing an issue? Is the value dropped at the end of this closure's first run? And furthermore, why does changing the line to take the cloned variable by reference make the compiler accept it?

                    if let Ok(config) = load_from_path(&config_path) {

Edit:

I may have actually just found the answer to my own question, but want to confirm. The definition for the function it is calling is:

pub fn load_from_path(path: impl AsRef<Path>) -> Result<Config>;

Because this accepts an impl AsRef<Path>, is this the attempted move on the value that is causing the error?

Yes, owned items like String [1] implement AsRef<Path>. You were moving the owned, captured config_path from out of the closure [2], making the closure unusable after that, just like when you move a non-Copy field out of your own structs. But if X implements AsRef<_>, so does &X and &&&&X, etc, so passing in a reference instead is the solution.


  1. and PathBuf etc. ↩︎

  2. the compiler-constructed datastructure that holds all the captured variables and borrows ↩︎

1 Like

The book covers this topic pretty well.

Oh sick, thank you.

Awesome. Thought that was the case, but wasn't quite sure. Thanks for the help.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.