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

File Watching

Watch a directory for changes

notify-badge cat-filesystem-badge

notify reports filesystem changes through one API. Under it sits a native backend. That is inotify on Linux, FSEvents on macOS, kqueue on the BSDs and ReadDirectoryChangesW on Windows. The watcher runs that backend on its own thread. It hands events back over a std::sync::mpsc channel. That is the standard library’s multi-producer, single-consumer queue. recommended_watcher takes the sending half and forwards each change to it as a Result<Event, Error>. Your thread keeps the receiving half and reads events by iterating it.

Passing RecursiveMode::Recursive to watch follows the entire subtree. Files created in nested directories are reported too. Every Event carries an EventKind and the affected paths. Matching on the kind keeps creations, data writes and removals. It discards metadata-only noise. The receiver blocks until the Watcher is dropped. A real tool keeps this loop running for its lifetime. Run it and edit files under the watched directory to watch events stream in.

use notify::event::{EventKind, ModifyKind};
use notify::{RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::mpsc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = std::env::args()
        .nth(1)
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("."));

    let (tx, rx) = mpsc::channel();
    let mut watcher = notify::recommended_watcher(tx)?;
    watcher.watch(&path, RecursiveMode::Recursive)?;

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

    for event in rx {
        let event = event?;
        match event.kind {
            EventKind::Create(_) => println!("created:  {:?}", event.paths),
            EventKind::Modify(ModifyKind::Data(_)) => {
                println!("modified: {:?}", event.paths)
            }
            EventKind::Remove(_) => println!("removed:  {:?}", event.paths),
            _ => {}
        }
    }

    Ok(())
}

Debounce a burst of file events

notify-debouncer-full-badge cat-filesystem-badge

A single logical change often surfaces as several raw events. Editors save by writing a temporary file and renaming it. Some backends emit separate create, modify and metadata notifications for one write. Acting on every raw event makes a watcher rebuild or reload far more often than the user intended.

notify-debouncer-full wraps a notify watcher and coalesces events that land within a time window. new_debouncer takes that window, an optional tick rate and a channel. Once a burst settles it delivers one deduplicated Vec<DebouncedEvent> instead of a flood. It also resolves renames and drops redundant events, so each path appears once per quiet period.

use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
use std::path::PathBuf;
use std::sync::mpsc;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = std::env::args()
        .nth(1)
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("."));

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

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

    for result in rx {
        match result {
            Ok(events) => {
                for event in events {
                    println!("{:?}: {:?}", event.kind, event.paths);
                }
            }
            Err(errors) => {
                for error in errors {
                    eprintln!("watch error: {error:?}");
                }
            }
        }
    }

    Ok(())
}