Unlocking Go’s io/fs: Unified File System Access and Real‑World Examples
This article explains Go 1.16’s io/fs package, its purpose as a unified file‑system abstraction, the core and extended interfaces it defines, practical use‑cases such as testing, embedded resources, memory and cloud storage, and provides multiple runnable code examples.
Purpose of the io/fs package
The io/fs package, introduced in Go 1.16, defines a set of interfaces that abstract file‑system operations. By programming against these interfaces the same code can work with local disks, in‑memory files, zip archives, or any custom implementation.
Core interfaces
fs.FS : represents a file system and provides Open(name string) (fs.File, error).
fs.File : an opened file exposing Read, Write, Close, etc.
fs.FileInfo : metadata such as name, size, modification time.
fs.DirEntry : a directory entry that may be a file or sub‑directory.
fs.FileMode : a bitmask describing permissions and type.
Extended interfaces
fs.GlobFSadds Glob(pattern string) ([]string, error) for wildcard matching. fs.ReadDirFS adds ReadDir(name string) ([]fs.DirEntry, error) to list directory contents. fs.ReadDirFile extends fs.File with ReadDir(n int) ([]fs.DirEntry, error). fs.ReadFileFS adds ReadFile(name string) ([]byte, error) for one‑shot reads. fs.StatFS adds Stat(name string) (fs.FileInfo, error). fs.SubFS adds Sub(dir string) (fs.FS, error) to create a view limited to a sub‑directory. fs.WalkDirFunc defines the callback signature used by fs.WalkDir.
Typical application scenarios
Accessing different file‑system types : the same code can read from the local disk, an in‑memory FS, or a zip archive because all implement fs.FS.
Testing : swapping a real FS with a mock (e.g., testing/fstest.MapFS) makes tests deterministic and fast.
Embedded resources : the standard embed package builds an embed.FS that can be accessed through io/fs APIs.
Code examples
Example 1 – Using fs.FS with os.DirFS
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
// Create a file system representing the current directory
fsys := os.DirFS(".")
// Open a file
f, err := fsys.Open("README.md")
if err != nil { log.Fatal(err) }
defer f.Close()
// Read its content
data := make([]byte, 100)
n, err := f.Read(data)
if err != nil { log.Fatal(err) }
fmt.Println(string(data[:n]))
}Example 2 – Getting file info with fs.File
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
f, err := fsys.Open("README.md")
if err != nil { log.Fatal(err) }
defer f.Close()
info, err := f.Stat()
if err != nil { log.Fatal(err) }
fmt.Println("File size:", info.Size())
}Example 3 – Listing directory entries with fs.ReadDir
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
entries, err := fs.ReadDir(fsys, ".")
if err != nil { log.Fatal(err) }
for _, entry := range entries {
fmt.Println("Name:", entry.Name())
fmt.Println("Is directory:", entry.IsDir())
}
}Example 4 – Glob pattern matching with fs.GlobFS
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
if globFS, ok := fsys.(fs.GlobFS); ok {
matches, err := globFS.Glob("*.go")
if err != nil { log.Fatal(err) }
fmt.Println("Go files:", matches)
}
}Example 5 – Reading a directory with fs.ReadDirFS
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
if readDirFS, ok := fsys.(fs.ReadDirFS); ok {
entries, err := readDirFS.ReadDir(".")
if err != nil { log.Fatal(err) }
fmt.Println("Directory contents:")
for _, entry := range entries { fmt.Println(entry.Name()) }
}
}Example 6 – Creating a sub‑file‑system with fs.SubFS
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
if subFS, ok := fsys.(fs.SubFS); ok {
sub, err := subFS.Sub("subdir")
if err != nil { log.Fatal(err) }
entries, err := fs.ReadDir(sub, ".")
if err != nil { log.Fatal(err) }
fmt.Println("Sub directory contents:")
for _, entry := range entries { fmt.Println(entry.Name()) }
}
}Example 7 – Walking a tree with fs.WalkDir
package main
import (
"fmt"
"io/fs"
"log"
"os"
)
func main() {
fsys := os.DirFS(".")
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil { return err }
fmt.Println("Walking:", path)
return nil
})
if err != nil { log.Fatal(err) }
}Interesting file‑system implementations
In‑memory file system
The testing/fstest package offers MapFS, which stores files in a Go map for fast, temporary use.
package main
import (
"fmt"
"io/fs"
"log"
"os"
"testing/fstest"
)
func main() {
fsys := fstest.MapFS{
"file1.txt": {Data: []byte("Hello, world!")},
"dir1/file2.txt": {Data: []byte("This is file2.")},
}
f, err := fsys.Open("file1.txt")
if err != nil { log.Fatal(err) }
defer f.Close()
data := make([]byte, 100)
n, err := f.Read(data)
if err != nil { log.Fatal(err) }
fmt.Println(string(data[:n]))
// Walk the in‑memory FS
err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil { return err }
fmt.Println("Walking:", path)
return nil
})
if err != nil { log.Fatal(err) }
}Third‑party library psanford/memfs at https://github.com/psanford/memfs provides similar functionality.
Embedded file system
The standard embed package builds an embed.FS that can be accessed via the same io/fs APIs.
package main
import (
"embed"
"fmt"
"io/fs"
"log"
)
//go:embed static
var staticFiles embed.FS
func main() {
f, err := staticFiles.Open("static/file1.txt")
if err != nil { log.Fatal(err) }
defer f.Close()
data := make([]byte, 100)
n, err := f.Read(data)
if err != nil { log.Fatal(err) }
fmt.Println(string(data[:n]))
// Walk embedded FS
err = fs.WalkDir(staticFiles, "static", func(path string, d fs.DirEntry, err error) error {
if err != nil { return err }
fmt.Println("Walking:", path)
return nil
})
if err != nil { log.Fatal(err) }
}Cloud‑storage file system
Libraries like gocloud.dev/blob expose cloud buckets (e.g., S3, GCS) as an fs.FS, enabling the same traversal and read operations.
package main
import (
"context"
"fmt"
"io/fs"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/gcs"
)
func main() {
bucketURL := "gs://my-bucket"
bucket, err := blob.OpenBucket(context.Background(), bucketURL)
if err != nil { log.Fatal(err) }
defer bucket.Close()
fsys := bucket.NewFS()
err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil { return err }
fmt.Println("Walking:", path)
return nil
})
if err != nil { log.Fatal(err) }
}References
psanford/memfs – https://github.com/psanford/memfs
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
