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.
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: