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(inout(self) : FsEventKind) -> FsEventKind

Parameters

NameTypeNotes
selfFsEventKind

Returns: FsEventKind

to_string : (FsEventKind) fn(inout(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

Watcher object
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.

Fields

NameTypeDescription
_handleFsEventHandle
_rootPath
_queueArrayList(FsEvent)
_headusize

Index of the first unread event in _queue; the list is compacted whenever it has been fully drained.

_activebool
_waitersArrayList(Waker)

Tasks suspended in next, woken by the next event or by close.

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(inout(self) : Watcher) -> Path

The watched path.

Parameters

NameTypeNotes
selfWatcher

Returns: Path

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

Whether the watch is still running.

Parameters

NameTypeNotes
selfWatcher

Returns: bool

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

Events queued and not yet read.

Parameters

NameTypeNotes
selfWatcher

Returns: usize

poll : (Watcher) fn(inout(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 last reference to the watcher goes away (Dispose below).

Parameters

NameTypeNotes
selfWatcher

Returns: unit

impl(Watcher, Stream(...))
Item : FsEvent
next : (Watcher) fn(self : 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).

Parameters

NameTypeNotes
selfWatcher
ioIo

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

impl(Watcher, Dispose(...))
dispose : (Watcher) fn(self : Watcher) -> unit

Release the resources self owns — a file descriptor, a socket, a lock, a buffer the allocator handed out. Called automatically when the value's owner drops it (a value type) or the last reference to it goes away (a reference type), so an implementor never calls it directly and must tolerate being the only one who ever does.

It must be safe to run exactly once, and a type that also exposes an explicit close/release is responsible for making the second call a no-op.

Parameters

NameTypeNotes
selfWatcher

Returns: unit

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
pathPath

The watched path.

optsWatchOptions
exnException

Returns: Watcher