Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reload configuration when the file changes

config-badge notify-badge cat-config-badge cat-filesystem-badge

A long-running service can pick up edits to its config file without a restart by watching the file and rebuilding its settings when it changes. This combines config for loading with notify for change detection. Watch a directory for changes covers the watching primitives.

new_debouncer collapses the burst of filesystem events an editor emits on save into a single notification, so the config is reloaded once per change rather than several times. Each notification triggers a fresh load; a parse error is logged and the previous settings are kept, so a malformed edit never takes down the running program.

use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::time::Duration;

use anyhow::Result;
use config::{Config, File};
use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Settings {
    host: String,
    port: u16,
}

fn load(path: &Path) -> Result<Settings, config::ConfigError> {
    Config::builder()
        .add_source(File::from(path))
        .build()?
        .try_deserialize()
}

fn main() -> Result<()> {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config.toml");

    let mut settings = load(&path)?;
    println!("serving {}:{}", settings.host, settings.port);

    let (tx, rx) = mpsc::channel();
    let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?;
    debouncer.watch(&path, RecursiveMode::NonRecursive)?;

    println!("watching {} for changes. Press Ctrl-C to stop.", path.display());

    for result in rx {
        match result {
            Ok(_events) => match load(&path) {
                Ok(updated) => {
                    settings = updated;
                    println!("reloaded {}:{}", settings.host, settings.port);
                }
                Err(error) => eprintln!("ignoring invalid config: {error}"),
            },
            Err(errors) => {
                for error in errors {
                    eprintln!("watch error: {error:?}");
                }
            }
        }
    }

    Ok(())
}