Update: Want to listen to a podcast about go:embed instead of reading a blog post? Check out Go Time episode 171. 简体中文版 by 涛叔 Tao Shu I have previously written about [how to use `//go:generate`][gg]. It exists to "to automate the running of tools to generate source code before compilation." Now there is a new feature in Go that eliminates many uses of source code generation. In addition to the remarkable new [flag.Func feature](/post/2020/add-func/), [Go 1.16][116] also introduced a new `//go:embed` directive that allows you to **include the contents of arbitrary files and directories** in your Go application. To demonstrate some of the capabilities of `//go:embed`, I have made an [example repo][exr] which I will explain in this post. [116]: https://golang.org/doc/go1.16 [gg]: /post/2016-11-27-how-to-use-go-generate/ [exr]: https://github.com/earthboundkid/exembed/ 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](https://pkg.go.dev/path#Match), so patterns like `files/*.html` will also work (but not `**/*.html` recursive globbing). You can read [the official docs](https://golang.org/pkg/embed/) for a complete technical explanation, so here let's take a look at some examples to see what's possible. ### Version information In [my post on `//go:generate`][gg], I showed how you could include version information with the Go linker. `//go:embed` gives us [an easy way to include version information from a version.txt file](https://github.com/earthboundkid/exembed/tree/main/version): ```go package main import ( _ "embed" "fmt" "strings" ) var ( Version string = strings.TrimSpace(version) //go:embed version.txt version string ) func main() { fmt.Printf("Version %q\n", Version) } ``` For a more complicated example, we can even [include version information conditionally](https://github.com/earthboundkid/exembed/tree/main/version-tags) based on whether a certain build tag is passed to the go tools. ```go // version_dev.go //go:build !prod // +build !prod package main var version string = "dev" ``` ```go // version_prod.go //go:build prod // +build prod package main import ( _ "embed" ) //go:embed version.txt var version string ``` ``` $ go run . Version "dev" $ go run -tags prod . Version "0.0.1" ``` ### Quine A [quine][] is a program that prints out its own source code. Let’s look at how [easily we can write one with `//go:embed`](https://github.com/earthboundkid/exembed/blob/main/quine/quine.go): [quine]: https://en.wikipedia.org/wiki/Quine_(computing) ```go package main import ( _ "embed" "fmt" ) //go:embed quine.go var src string func main() { fmt.Print(src) } ``` When we run this program, it prints out itself. ### Embedding complex structs If we have some complex information we want to precompute, we can save that information in our Go project by writing it out into a .go file with `//go:generate` and templates (as shown in [my post about `//go:generate`][gg]), or we can [save it to a serialization format understood by Go](https://github.com/earthboundkid/exembed/tree/main/gob) and load up the serialized data on start up: ```go package main import ( "bytes" _ "embed" "encoding/gob" "fmt" ) var ( // File value.gob contains some complicated data // which we have precomputed and saved. //go:embed value.gob b []byte s = func() (s struct { Number float64 Weather string Alphabet []string }) { dec := gob.NewDecoder(bytes.NewReader(b)) if err := dec.Decode(&s); err != nil { panic(err) } return }() ) func main() { fmt.Printf("s: %#v\n", s) } ``` ### Website files This is probably going to be one of the biggest application for `//go:embed`. We can now [include all the static files](https://github.com/earthboundkid/exembed/tree/main/web) or templates needed for our website in a single executable. We can even toggle between reading files on disk and reading embedded files on the fly based on command line arguments: ```go package main import ( "embed" "io/fs" "log" "net/http" "os" ) func main() { useOS := len(os.Args) > 1 && os.Args[1] == "live" http.Handle("/", http.FileServer(getFileSystem(useOS))) http.ListenAndServe(":8888", nil) } //go:embed static var embededFiles embed.FS func getFileSystem(useOS bool) http.FileSystem { if useOS { log.Print("using live mode") return http.FS(os.DirFS("static")) } log.Print("using embed mode") fsys, err := fs.Sub(embededFiles, "static") if err != nil { panic(err) } return http.FS(fsys) } ``` Note that we need to strip off the directory prefix from the `embed.FS` with `fs.Sub` so that it matches what is produced by the `os.DirFS`. Ben Johnson has already [released a helper package](https://github.com/benbjohnson/hashfs) that makes `embed.FS` work with [content hashing](/post/2017/hugo-asset-pipeline/). Here is another example that shows [an embedded template](https://github.com/earthboundkid/exembed/tree/main/templ): ```go package main import ( "embed" "os" "text/template" ) //go:embed *.tmpl var tpls embed.FS func main() { name := "en.tmpl" if len(os.Args) > 1 { name = os.Args[1] + ".tmpl" } arg := "World" if len(os.Args) > 2 { arg = os.Args[2] } t, err := template.ParseFS(tpls, "*") if err != nil { panic(err) } if err = t.ExecuteTemplate(os.Stdout, name, arg); err != nil { panic(err) } } ``` With `en.tmpl` having the contents `Hello {{ . }}, how are you today?` and `jp.tmpl` having the contents `こんにちは{{ . }}。お元気ですか。`, we get this output: ``` $ go run ./main.go Hello World, how are you today? $ go run ./main.go jp ワールド こんにちはワールド。お元気ですか。 ``` ### Gotchas There are some gotchas with embedding to be aware of. First of all, you must import the `embed` package in any file that uses an embed directive. So a file [like this](https://github.com/earthboundkid/exembed/blob/main/bad/missing-embed.go) won’t work: ```go package main import ( "fmt" ) //go:embed file.txt var s string func main() { fmt.Print(s) } ``` ``` $ go run missing-embed.go # command-line-arguments ./missing-embed.go:8:3: //go:embed only allowed in Go files that import "embed" ``` On the other hand, the usual Go rules forbidding unused imports apply. If you need to import embed but not refer to any exported identifiers in it, you should use `import _ "embed"` to tell Go to import embed even though it doesn’t look like it’s being used. Second, you can only use `//go:embed` for variables at the package level, not within functions or methods, so a program [like this](https://github.com/earthboundkid/exembed/blob/main/bad/bad-level.go) won’t compile: ```go package main import ( _ "embed" "fmt" ) func main() { //go:embed file.txt var s string fmt.Print(s) } ``` ``` $ go run bad-level.go # command-line-arguments ./bad-level.go:9:4: go:embed cannot apply to var inside func ``` 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 this reason, **using `//go:embed dir/*` is almost always a mistake**. Use `//go:embed dir` or `//go:embed dir/*.ext` as needed instead. For security reasons, Go also won't follow symbolic links or go up a directory when embedding. - - - - There are no limits to the possible applications of embedding. For example, what if you read the documentation and licensing info for your command line application out of the repo's **README** file? How about storing [database queries](http://sqlc.dev) for your application as **embedded `.sql` files**? Or you could write **an overlay FS** to combine a built-in `embed.FS` with user-supplied override files… These are just a few ideas scratching the surface. I’m sure we’ll see a lot of clever and unexpected uses for it as time goes on. Go 1.16 was released [Feb. 16, 2021][116rel]. If you haven't already upgraded, try it out today. [116rel]: https://blog.golang.org/go1.16

I wrote a tutorial about file embedding, a new feature in #golang 1.16.https://t.co/91SwBeouto

— Carl Johnson (@carlmjohnson) January 30, 2021