Use //go:embed in Go 1.16

//go:embed

Go 1.16 introduced a new //go:embed directive that allows you to include the contents of arbitrary files and directories in your Go application.

The basic idea of embedding is that by adding a special comment to your code, Go will know to include a file or files. The comment should look like //go:embed FILENAME(S) and be followed by a variable of the type you want to embed: string or []byte for an individual file or embed.FS for a group of files.

The go:embed directive understands Go file globs, so patterns like files/.html will also work (but not **/.html recursive globbing).

You can read the official docs embed · pkg.go.dev - https://pkg.go.dev/embed for a complete technical explanation, so here let’s take a look at some examples to see what’s possible.

Usage

A //go:embed directive above a variable declaration specifies which files to embed, using one or more path.Match patterns.

There are some rules or limits:

Like other directives, there can be no spaces between // and go:embed. (No error will be reported, but the instruction will be ignored by the compiler)

The directive must immediately precede a line containing the declaration of a single variable. Only blank lines and ‘//’ line comments are permitted between the directive and the declaration.

The type of the variable can only be string, []byte or embed.FS, even the aliases of these three types will not work.

The patterns are interpreted relative to the package directory containing the source file. The path separator is a forward slash, even on Windows systems.

The //go:embed directive can be used with both exported and unexported variables, depending on whether the package wants to make the data available to other packages. It can only be used with global variables at package scope, not with local variables.

Patterns must not match files outside the package’s module, such as '.git/*’ or symbolic links. Matches for empty directories are ignored. After that, each pattern in a //go:embed line must match at least one file or non-empty directory.

If any patterns are invalid or have invalid matches, the build will fail.

Embedding one file into a string:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.go

package main

import (
_ "embed"
"fmt"
)

//go:embed hello.txt
var s string

func main() {
fmt.Printf("s is %s", s)
}

Run main.go.

1
2
$ go run main.go
s is s

Embedding one file into a slice of bytes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.go

package main

import (
_ "embed"
"fmt"
)

//go:embed hello.txt
var b []byte

func main() {
fmt.Printf("b is %s", b)
}

Run main.go.

1
2
$ go run main.go
b is b

Embedded one or more files into a file system

The FS type enables embedding a tree of files, such as a directory of static web server content, as in the example follow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.go

package main

import (
"embed"
"fmt"
)

//go:embed hello.txt
var f embed.FS

func main() {
fmt.Printf("f is %s", f)
}

Run main.go.

1
2
$ go run main.go
b is b

The //go:embed directive accepts multiple space-separated patterns for brevity, but it can also be repeated, to avoid very long lines when there are many patterns.

1
2
3
4
// content holds our static web server content.
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

The variable in the above example is almost equivalent to:

1
2
3
// content is our static web server content.
//go:embed image template html/index.html
var content embed.FS

Patterns may not contain ‘.’ or ‘…’ or empty path elements, nor may they begin or end with a slash.

To match everything in the current directory, use ‘*’ instead of ‘.’. To allow for naming files with spaces in their names, patterns can be written as Go double-quoted or back-quoted string literals.

For a directory, the directory will be the root, and all files and subdirectories will be embedded recursively.

File Systems

FS implements the io/fs package’s FS interface, so it can be used with any package that understands file systems, including net/http, text/template, and html/template.

For example, given the content variable in the example above, we can write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.go

package main

import (
"embed"
"log"
"net/http"
)

//go:embed static
var local embed.FS

func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))
log.Fatal(http.ListenAndServe(":8989", nil))
}

Or use with html/template.

1
template.ParseFS(content, "*.tmpl")

Gotchas

There are some gotchas with embedding to be aware of.

Forget to import the embed package

First of all, you must import the embed package in any file that uses an embed directive. So a file like this won’t work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// missing-embed.go

package main

import (
"fmt"
)

//go:embed file.txt
var s string

func main() {
fmt.Print(s)
}

run missing-embed.go.

1
2
3
$ go run missing-embed.go
# command-line-arguments
./missing-embed.go:8:3: //go:embed only allowed in Go files that import "embed"

Use within functions or methods

Second, you can only use //go:embed for variables at the package level, not within functions or methods, so a program like this won’t compile:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
_ "embed"
"fmt"
)

func main() {
//go:embed file.txt
var s string
fmt.Print(s)
}

Run bad-level.go.

1
2
3
$ go run bad-level.go
# command-line-arguments
./bad-level.go:9:4: go:embed cannot apply to var inside func

Ignore some files by default

Third, when you include a directory, it won’t include files that start with . or _, but if you use a wildcard, like dir/*, it will include all files that match, even if they start with . or _.

Bear in mind that accidentally including Mac OS’s .DS_Store files may be a security problem in circumstances where you want to embed files in a webserver but not allow users to see a list of all the files.

For security reasons, Go also won’t follow symbolic links or go up a directory when embedding.

References

[1] embed · pkg.go.dev - https://pkg.go.dev/embed

[2] How to Use //go:embed · The Ethically-Trained Programmer - https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/

[3] Go 1.16 is released - The Go Blog - https://blog.golang.org/go1.16

[4] The Go Programming Language - https://golang.org/

[5] 提前试用将在 Go1.16 中发布的内嵌静态资源功能 - https://polarisxu.studygolang.com/posts/go/dynamic/go-embed-try/