Module fs/walker

fs/walker
Stability: unstable — Rust's `walkdir` is a lazy `Iterator<Item = Result<DirEntry>>` and this is a materialised list, which decides two things a caller cannot work around: `walk` over a large tree allocates the entire tree (there is no "stop after the first match" — `glob` below pays exactly that cost), and ONE unreadable subdirectory throws out of the whole walk instead of surfacing as one failed item. `follow_symlinks` is also disabled outright on Windows (`issues/walker-follow-symlinks-windows-unsupported.md`). Freezing follows a lazy cursor with per-entry errors and Windows symlink descent, not a release count. — stable modules only change additively; this one may still change.

Recursive directory traversal.

Example

{ walk, WalkEntry } :: import "std/fs/walker";

main :: (fn(io : Io, exn : Exception) -> unit)({
  entries := io.await(walk(Path.new(`/tmp`), io), { io, exn });
  i := usize(0);
  while runtime((i < entries.len())), {
    e := entries(i);
    println(e.path);
    i = (i + usize(1));
  };
});

The walk is EAGER and iterative: walk_with drains the whole tree into one ArrayList(WalkEntry) before its future resolves, using an explicit stack rather than recursion, so depth costs heap and not C stack. The root itself is never an entry — only what is inside it. Order is the filesystem's, further shuffled by the stack (the last subdirectory found is the first descended), so nothing here is sorted; sort if you need determinism.

Stability

unstable — Rust's walkdir is a lazy Iterator<Item = Result<DirEntry>> and this is a materialised list, which decides two things a caller cannot work around: walk over a large tree allocates the entire tree (there is no "stop after the first match" — glob below pays exactly that cost), and ONE unreadable subdirectory throws out of the whole walk instead of surfacing as one failed item. follow_symlinks is also disabled outright on Windows (issues/walker-follow-symlinks-windows-unsupported.md). Freezing follows a lazy cursor with per-entry errors and Windows symlink descent, not a release count.

Types

WalkEntry struct
WalkEntry

Entry returned by the directory walker.

Fields

NameTypeDescription
pathPath

Full path, built as walk-root + every intervening directory name + name. It carries the root SPELLED AS GIVEN, so a walk of ./x reports ./x/... and a walk of /abs/x reports /abs/x/... — this is not canonicalised.

nameString

The entry's own name, with no directory part.

depthu32

How deep below the walk root this entry sits: 0 for the root's own children, 1 for their children, and so on. WalkOptions.max_depth is a bound on exactly this number.

file_typeFileType

The entry's kind, WITHOUT following a final symlink — a link is always .Symlink, whatever follow_symlinks is set to. That option governs descent, not classification, so a followed directory symlink appears once as .Symlink and its contents appear beneath it.

WalkOptions struct
WalkOptions

Options controlling directory walk behavior.

Fields

NameTypeDescription
max_depthOption(u32)

Deepest WalkEntry.depth to report, or .None for unlimited. A directory deeper than this is not read at all, so .Some(0) lists the root's immediate children and nothing else.

follow_symlinksbool

Descend THROUGH directory symlinks as well as reporting them. Off by default, and ignored entirely on Windows (see the module header). Following is cycle-guarded by canonicalising each followed target and entering it at most once, so a link pointing at an ancestor terminates instead of recursing forever — but it costs a stat plus a canonicalize per symlink encountered.

include_dirsbool

Report directories as entries of their own, not just their contents. Directories are DESCENDED either way; this only decides whether they appear in the result. remove_dir_all needs them, which is why the default is true — the opposite of what a file-oriented caller wants.

patternOption(String)

When set, only entries whose path RELATIVE TO THE WALK ROOT matches this glob (std/glob semantics; ** crosses separators) are returned. Directories are still DESCENDED regardless, so **/x.txt finds deep files even though intermediate dirs do not match.

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

Unlimited depth, symlinks reported but not followed, directories included, no pattern — what walk uses.

Returns: WalkOptions

Functions

walk function
fn(root : Path, io : Io) -> Impl(Future(ArrayList(WalkEntry), IoExn))

Walk a directory tree with default options.

Parameters

NameTypeNotes
rootPath
ioIo

Returns: Impl(Future(ArrayList(WalkEntry), IoExn))

walk_cstr function
fn(root : *u8, io : Io) -> Impl(Future(ArrayList(WalkEntry), IoExn))

walk taking a NUL-terminated C string as the root. See walk_with_cstr for what an undecodable pointer does.

Parameters

NameTypeNotes
root*u8
ioIo

Returns: Impl(Future(ArrayList(WalkEntry), IoExn))

walk_with function
fn(root : Path, options : WalkOptions, io : Io) -> Impl(Future(ArrayList(WalkEntry), IoExn))

Walk the tree under root with explicit options, returning every entry found — the general form behind walk and glob.

Async: awaited on the single-threaded event loop, and it throws IoExn as soon as any directory in the tree cannot be read (including root itself), discarding the entries collected so far. A tree the caller has only partial access to therefore needs read_dir per level rather than this.

Symlink handling is deferred by design: a directory's symlinks are collected while its entries are scanned and resolved in a second pass over that same directory, because the stat + canonicalize the follow decision needs are awaits, and an await that deep inside the scan loop trips the async state machine (issues/async-nested-cond-await-duplicate-while-labels.md).

Parameters

NameTypeNotes
rootPath
optionsWalkOptions
ioIo

Returns: Impl(Future(ArrayList(WalkEntry), IoExn))

walk_with_cstr function
fn(root : *u8, options : WalkOptions, io : Io) -> Impl(Future(ArrayList(WalkEntry), IoExn))

walk_with taking a NUL-terminated C string as the root, for callers already holding one from a C API. Identical otherwise: the bytes are copied into a Path immediately. An undecodable or unterminated pointer yields an EMPTY path rather than an error (Path.from_cstr), which then throws when the walk tries to read it.

Parameters

NameTypeNotes
root*u8
optionsWalkOptions
ioIo

Returns: Impl(Future(ArrayList(WalkEntry), IoExn))

glob function
fn(pattern : String, io : Io) -> Impl(Future(ArrayList(Path), IoExn))

Expand a glob pattern against the filesystem — the Python/Node meaning: walk from the pattern's static prefix and return every matching path (files, dirs and symlinks alike; ** crosses directories). Throws the walker's IoExn when the static base directory cannot be read.

Parameters

NameTypeNotesDescription
patternString

When set, only entries whose path RELATIVE TO THE WALK ROOT matches this glob (std/glob semantics; ** crosses separators) are returned. Directories are still DESCENDED regardless, so **/x.txt finds deep files even though intermediate dirs do not match.

ioIo

Returns: Impl(Future(ArrayList(Path), IoExn))

remove_dir_all function
fn(path : Path, io : Io) -> Impl(Future(unit, IoExn))

Recursively remove a directory and everything under it (std::fs::remove_dir_all). Does NOT follow symlinks — a symlinked directory is removed as a LINK, its target untouched.

Lives HERE rather than in std/fs/dir because the implementation is the walker (collect the whole tree, delete deepest-first) and fs/walker imports fs/dir — the reverse import would be a cycle. This is the same implementation the compiler ran in production as a private helper (src/fetch.yo) before it moved into std.

Parameters

NameTypeNotesDescription
pathPath

Full path, built as walk-root + every intervening directory name + name. It carries the root SPELLED AS GIVEN, so a walk of ./x reports ./x/... and a walk of /abs/x reports /abs/x/... — this is not canonicalised.

ioIo

Returns: Impl(Future(unit, IoExn))

fn(path : str, io : Io) -> Impl(Future(unit, IoExn))

remove_dir_all (str path variant).

Parameters

NameTypeNotesDescription
pathstr

Full path, built as walk-root + every intervening directory name + name. It carries the root SPELLED AS GIVEN, so a walk of ./x reports ./x/... and a walk of /abs/x reports /abs/x/... — this is not canonicalised.

ioIo

Returns: Impl(Future(unit, IoExn))