Module fs/watch

fs/watch
Stability: unstable — may still change; see below.

File-system change notifications (fs.watch, plans/archive/STD_API_AUDIT.md §7 P1).

A Watcher observes one path — a directory (entries created, removed, renamed or written) or a single file — through the runtime's native backend (inotify on Linux, kqueue on macOS, ReadDirectoryChangesW on Windows; the std/sys/events layer). Events are delivered on the event loop and queued inside the watcher; read them with the non-blocking poll() or the awaitable next(io). next is the Stream trait's method (std/async/stream.yo), so a watcher composes: w.filter(…).map(…).take(n).

{ watch, WatchOptions, FsEventKind } :: import("std/fs/watch");
w := watch(Path.new(`./logs`), WatchOptions.defaults(), exn);
ev := io.await(w.next(io), io); // Option(FsEvent) — .None once closed
match(ev, .Some(e) => println(`${e.name} ${e.kind}`), .None => ());
w.close();

close() is idempotent and runs automatically when the last reference to the watcher goes away, so a watcher that simply falls out of scope stops its native watch rather than leaving the event loop calling back into freed memory.

Event kinds follow the backend's (and libuv's) two-way split: Rename covers create / delete / move, Change covers a write or metadata change. name is the entry name relative to a watched directory (the file's own name when a file is watched) on every platform — on macOS the runtime diffs a snapshot of the directory and reports each changed entry; an event about the directory itself (or a vanished root) carries an empty name. Platforms coalesce differently, so a single write may surface as one or several Changes — treat the stream as "something happened", not as an exact journal.

Stability

unstable — shipped in v0.2.20 and still unstable at v0.2.24. This is a deliberate extension of the one-release window, not drift: the Windows backend is not delivered, and the shape of Windows change delivery (ReadDirectoryChangesW batches renames as paired events) is the one thing most likely to move Change. macOS and Linux are usable today. Freezing follows Windows delivery, not a release count.

Types

FsEventKind

What happened to the entry.

Variants

VariantFieldsDescription
Rename

Created, removed or moved (the backend's "rename" class).

Change

Contents or metadata changed.

Trait Implementations

impl(FsEventKind, ToString(...))
to_string : ( self -> match( self, .Rename => String.from("rename"), .Change => String.from("change") ) )
Methods
clone : (FsEventKind) fn(self : &FsEventKind) -> FsEventKind

Parameters

NameTypeNotes
selfFsEventKind

Returns: FsEventKind

to_string : (FsEventKind) fn(self : &FsEventKind) -> String

Render self as the text a USER should read — Rust's Display::fmt, not its Debug. Hand-written (or generated by derive(Error) from a per-variant format string) whenever the structural form would be wrong.

Parameters

NameTypeNotes
selfFsEventKind

Returns: String

FsEvent struct
FsEvent

One notification.

Fields

NameTypeDescription
nameString

Entry name relative to the watched directory (or the watched file's own name); empty when the backend reports no name.

kindFsEventKind

Which of the backend's two classes this was. Platforms coalesce differently, so a Rename says "created, removed or moved" rather than which of the three.

WatchOptions struct
WatchOptions

Watch settings.

Fields

NameTypeDescription
recursivebool

Watch the whole tree below a directory. Native on macOS/Windows; on Linux inotify is per directory, so this is best-effort there.

impl(WatchOptions, ...)
defaults : (WatchOptions) fn() -> WatchOptions

Watch only the named directory itself — recursive : false. That is the conservative default because recursive is best-effort on Linux (inotify is per directory), so a program written against the default behaves the same on every platform.

Returns: WatchOptions

Methods
clone : (WatchOptions) fn(self : &WatchOptions) -> WatchOptions

Parameters

NameTypeNotes
selfWatchOptions

Returns: WatchOptions

Watcher struct
Watcher

A live watch. Create with watch, drain with poll/next, and close when done — closing stops the native watch and makes next resolve .None for any waiter.

A move-only value (its state is a Box): one owner stops the watch, and a copy is E0901. close takes self : &Self and stops the watch inside the box, so a Watcher is never Sync.

Fields

NameTypeDescription
_cellBox(_WatcherState)

Trait Implementations

impl(generic(S : Type), where(S <: Stream), S : (Stream))
map : fn(generic(A, B, F) self : S : (Stream), f : F : (Fn(A) -> B)) -> StreamMap(S : (Stream), B, F : (Fn(A) -> B))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> B)

Returns: StreamMap(S : (Stream), B, F : (Fn(A) -> B))

filter : fn(generic(A, F) self : S : (Stream), f : F : (Fn(A) -> bool)) -> StreamFilter(S : (Stream), F : (Fn(A) -> bool))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> bool)

Returns: StreamFilter(S : (Stream), F : (Fn(A) -> bool))

filter_map : fn(generic(A, B, F) self : S : (Stream), f : F : (Fn(A) -> Option(B))) -> StreamFilterMap(S : (Stream), B, F : (Fn(A) -> Option(B)))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> Option(B))

Returns: StreamFilterMap(S : (Stream), B, F : (Fn(A) -> Option(B)))

take : fn(generic(A) self : S : (Stream), n : usize) -> StreamTake(S : (Stream))

Parameters

NameTypeNotes
selfS : (Stream)
nusize

Returns: StreamTake(S : (Stream))

skip : fn(generic(A) self : S : (Stream), n : usize) -> StreamSkip(S : (Stream))

Parameters

NameTypeNotes
selfS : (Stream)
nusize

Returns: StreamSkip(S : (Stream))

for_each : fn(generic(A, F) self : S : (Stream), f : F : (Fn(A) -> unit), io : Io) -> Impl(Future(unit, Io))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> unit)
ioIo

Returns: Impl(Future(unit, Io))

collect : fn(generic(A) self : S : (Stream), io : Io) -> Impl(Future(ArrayList(A), Io))

Parameters

NameTypeNotes
selfS : (Stream)
ioIo

Returns: Impl(Future(ArrayList(A), Io))

impl(Watcher, ...)
path : (Watcher) fn(self : &Watcher) -> Path

The watched path.

Parameters

NameTypeNotes
selfWatcher

Returns: Path

is_active : (Watcher) fn(self : &Watcher) -> bool

Whether the watch is still running.

Parameters

NameTypeNotes
selfWatcher

Returns: bool

pending : (Watcher) fn(self : &Watcher) -> usize

Events queued and not yet read.

Parameters

NameTypeNotes
selfWatcher

Returns: usize

poll : (Watcher) fn(self : &Watcher) -> Option(FsEvent)

Take the next queued event without waiting; .None when the queue is empty.

Parameters

NameTypeNotes
selfWatcher

Returns: Option(FsEvent)

close : (Watcher) fn(self : &Watcher) -> unit

Stop the native watch. Queued events stay readable through poll; next no longer waits. Idempotent, and called for you when the watcher is dropped.

Parameters

NameTypeNotes
selfWatcher

Returns: unit

impl(Watcher, Stream(...))
Item : FsEvent
next : (Watcher) fn(self : &mut Watcher, io : Io) -> Impl(Future(Option(FsEvent), Io))

Wait for the next event. Resolves .None once the watcher is closed and its queue drained. PARKS until the event callback (or close) wakes it — no nested event loop, and no CPU while it waits. It used to re-yield in a loop, which kept the task always runnable: a waiting watcher spun a core and the loop never blocked (issues/fixed/poll-and-fs-event-watches-starve-while-the-loop-waits.md).

The future borrows the watcher (&mut self): await it where it is made, or hand the watcher to a combinator that owns it.

Parameters

NameTypeNotes
selfWatcher
ioIo

Returns: Impl(Future(Option(FsEvent), Io))

Functions

watch function
fn(path : &Path, opts : WatchOptions, exn : Exception) -> Watcher

Start watching path. Throws the backend's IoError (e.g. NotFound) through exn.

Parameters

NameTypeNotesDescription
path&Path

The watched path.

optsWatchOptions
exnException

Returns: Watcher