io/fs

Guided tour · I/O & Files · pkg.go.dev →

The abstract filesystem interface. Lets one code path work over disk, embed.FS, a zip, a test double, or anything else.

The interfaces

fs.FS is the minimum: Open(name) (File, error). Extended interfaces (ReadDirFS, StatFS, ReadFileFS, SubFS, GlobFS) let implementations opt into faster paths.

Who implements fs.FS?

// os.DirFS("/path")          — a directory on disk
// embed.FS                   — files embedded at build time
// zip.Reader                 — a zip archive
// fstest.MapFS               — an in-memory map for tests

Helpers that work on any fs.FS

ReadFile, ReadDir, Stat, Sub

data, _ := fs.ReadFile(myFS, "config.json")
entries, _ := fs.ReadDir(myFS, "templates")
info, _ := fs.Stat(myFS, "x.txt")
sub, _ := fs.Sub(myFS, "static")   // scope to a subdir

WalkDir — the portable version of filepath.WalkDir

Works on ANY fs.FS, not just real disks. Returns fs.SkipDir to prune a subtree.

fs.WalkDir(myFS, ".", func(p string, d fs.DirEntry, err error) error {
    if err != nil { return err }
    fmt.Println(p)
    return nil
})

Glob

matches, _ := fs.Glob(myFS, "*.md")

Using os.DirFS for CLI + embed.FS for shipped assets

Same code path, different backing

var root fs.FS
if *live {
    root = os.DirFS("./public")   // read from disk in dev
} else {
    root = publicEmbed            // read from embed.FS in prod
}

http.ListenAndServe(":8080", http.FileServerFS(root))