Module fs/walker

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

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(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
root&Path
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/fixed/async-nested-cond-await-duplicate-while-labels.md).

Parameters

NameTypeNotes
root&Path
options&WalkOptions
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
options&WalkOptions
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
pattern&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.

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
path&Path

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))