How to hot reload a struct from file changes

Viewed 544

I'm trying to do hot reloading when a file changes, but I'm getting this error: expected a closure that implements the Fn trait, but this closure only implements FnMut this closure implements FnMut, not Fn

Seems to be unhappy with the closure I'm passing to the new_immediate function from this library:

notify = { version = "5.0.0-pre.4", features = ["serde"] }

My code:

use announcer::messages::{load_config, save_config, Config, Message};
use notify::{
    event::ModifyKind, Error, Event, EventFn, EventKind, RecommendedWatcher, RecursiveMode, Watcher,
};
use tide::{http, Body, Response};

const CONFIG_PATH: &str = "config.json";

#[async_std::main]
async fn main() -> tide::Result<()> {
    let mut config = load_config(CONFIG_PATH).unwrap();

    let mut watcher: RecommendedWatcher =
        Watcher::new_immediate(|result: Result<Event, Error>| {
            let event = result.unwrap();
            
            if event.kind == EventKind::Modify(ModifyKind::Any) {
                config = load_config(CONFIG_PATH).unwrap();
            }
        })?;

    watcher.watch(CONFIG_PATH, RecursiveMode::Recursive)?;

    let mut app = tide::with_state(config);
    app.listen("127.0.0.1:8080").await?;

    Ok(())
}

I asked in the Rust Discord beginners chat and 17cupsofcoffee said I should be using a mutex but I have no idea how to do that.

1 Answers

The issue here is that you're spawning this watching function in another thread, and you might write to it in one thread while reading to it in another, causing a race condition. You should use a Mutex, and lock it to get a guard that lets you read from it/write to it. Since tide's global state also needs Clone, you should also wrap it in an Arc, a thread-safe reference-counted pointer:

use announcer::messages::{load_config, save_config, Config, Message};
use notify::{
    event::ModifyKind, Error, Event, EventFn, EventKind, RecommendedWatcher, RecursiveMode, Watcher,
};
use tide::{http, Body, Response};
// necessary imports
use std::sync::{Arc, Mutex};

const CONFIG_PATH: &str = "config.json";

#[async_std::main]
async fn main() -> tide::Result<()> {
    // we store it in an Arc<Mutex<T>>
    let config = Arc::new(Mutex::new(load_config(CONFIG_PATH).unwrap()));
    let cloned_config = Arc::clone(&config);

    let mut watcher: RecommendedWatcher =
        Watcher::new_immediate(move |result: Result<Event, Error>| {
            let event = result.unwrap();
            
            if event.kind == EventKind::Modify(ModifyKind::Any) {
                // we lock the mutex to acquire a mutable guard and write to that
                match load_config(CONFIG_PATH) {
                    Ok(new_config) => *cloned_config.lock().unwrap() = new_config,
                    Err(error) => println!("Error reloading config: {:?}", error),
                }
        })?;

    watcher.watch(CONFIG_PATH, RecursiveMode::Recursive)?;
    
    let mut app = tide::with_state(config);
    app.listen("127.0.0.1:8080").await?;

    Ok(())
}

Then, whenever you need to read from the state, you just lock the mutex:

let config_guard = req.state().lock().unwrap();
println!("{:?}", config_guard.foo);

For additional reference:

Related