Docs/Files & system

Paths & directories

Build paths and list, create, or remove directories.

On this page

Paths are plain strings, and valk.fs has functions to join and split them and to work with directories. Relative paths start from the process's working directory.

Join paths#

fs.add joins two parts with exactly one separator. The fs.Path mode offers the same as methods, so calls can be chained:

Valk
use valk.fs

fn main() {
    println(fs.add("reports/", "/daily.txt")) // reports/daily.txt

    let dir: fs.Path = "reports"
    let file = dir.add("2026").add("daily.txt")
    println(file)          // reports/2026/daily.txt
    println(file.pop())    // reports/2026
}

A Path converts to and from String by itself; fs.path("x") makes one without a type hint. Building a path never touches the disk.

Split a path#

Valk
use valk.fs

fn main() {
    let path = "/var/www/site/logo.png"
    println(fs.basename(path))         // logo.png
    println(fs.dir_of(path))           // /var/www/site
    println(fs.extension(path))        // png
    println(fs.extension(path, true))  // .png
    println(fs.relative("/a/b/c", "/a/d")) // ../b/c
    println(fs.mime_type("png"))       // image/png
}
Function Returns
fs.resolve(path) The absolute path with ., .. and double separators folded
fs.realpath(path) The absolute path with symlinks resolved (the path must exist)
fs.relative(path, base) path as seen from the directory base
fs.cwd() / fs.chdir(path) The working directory, or change it
fs.home_dir() The user's home directory
fs.exe_path() / fs.exe_dir() The running executable, or its directory

fs.PATH_DIV is the separator of the target: \ on Windows, / elsewhere.

Create and delete directories#

Valk
use valk.fs

fn main() {
    fs.create_dir_all("build/cache/images") ! return // like mkdir -p
    fs.create_dir("build/logs") ! return             // the parent must exist
    println(fs.is_dir("build/cache")) // true
    fs.delete_dir("build/logs") ! return             // must be empty
    fs.delete_all("build") ! return                  // everything inside too
    println(fs.exists("build"))       // false
}

Both create functions take the permission bits as an optional second argument (default 0c755).

List a directory#

fs.files_in returns an array of paths. Its options pick recursion, files, directories, and names relative to the directory:

Valk
use valk.fs

fn main() {
    fs.create_dir_all("site/css") ! return
    fs.write("site/index.html", "") ! return
    fs.write("site/css/main.css", "") ! return

    let all = fs.files_in("site", recursive: true, dirs: false, relative: true) ! return
    all.sort()
    println(all.join(", ")) // css/main.css, index.html

    let css = fs.glob("site/**/*.css")
    println(css.join(", "))  // site/css/main.css
}

fs.glob understands *, ?, [a-z] and ** for any number of directories, and returns the matches sorted. The order of files_in is not defined.

For a large directory, fs.read_dir returns one name at a time without building an array. It skips . and ..:

Valk
use valk.fs

fn main() {
    fs.create_dir_all("inbox") ! return
    fs.write("inbox/a.txt", "") ! return
    let entries = fs.read_dir("inbox") ! return
    defer entries.close() _
    while true {
        let name = entries.next() ! return
        if !isset(name) : break
        println(name) // a.txt
    }
}

The iterator closes itself at the end; the defer covers stopping early.

Temporary files#

Valk
use valk.fs

fn main() {
    let dir = fs.create_temp_dir("myapp-") ! return
    let file = fs.create_temp_file("upload-", ".json", dir) ! return
    fs.write(file, "{}") ! return
    println(fs.exists(file)) // true
    fs.delete_all(dir) ! return
}

Both create a new, empty entry with a unique name in fs.temp_dir() (or the directory given last) and return its path. Delete them when you are done.