Embed directive: incorporando arquivos no binário
1. Introdução à diretiva //go:embed
A partir do Go 1.16, a linguagem introduziu uma funcionalidade nativa e elegante para incorporar arquivos diretamente no binário compilado: a diretiva //go:embed. Antes dessa adição, desenvolvedores precisavam recorrer a ferramentas externas como go-bindata, packr ou statik para resolver o problema de distribuir arquivos estáticos junto com a aplicação.
O principal problema resolvido pelo embed é a eliminação da dependência de caminhos relativos e arquivos externos em tempo de execução. Com essa diretiva, o binário gerado torna-se autossuficiente — não importa onde ele seja executado, os arquivos incorporados estarão sempre disponíveis.
A diretiva suporta três tipos de dados para incorporação:
- string: para arquivos de texto simples
- []byte: para dados binários ou quando mutabilidade é necessária
- embed.FS: um sistema de arquivos virtual completo para múltiplos arquivos e subdiretórios
2. Sintaxe básica e tipos de incorporação
Incorporando como string
Ideal para arquivos de texto como JSON, HTML ou configurações:
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var version string
func main() {
fmt.Println("Versão:", version)
}
Incorporando como []byte
Perfeito para dados binários ou quando você precisa modificar o conteúdo:
package main
import (
_ "embed"
"fmt"
)
//go:embed logo.png
var logo []byte
func main() {
fmt.Printf("Tamanho do logo: %d bytes\n", len(logo))
}
Incorporando como embed.FS
Para múltiplos arquivos e subdiretórios, use embed.FS:
package main
import (
"embed"
"fmt"
)
//go:embed templates/*.html
var templateFS embed.FS
func main() {
data, _ := templateFS.ReadFile("templates/index.html")
fmt.Println(string(data))
}
3. Padrões de caminho e limitações
Os caminhos na diretiva //go:embed são relativos ao diretório do pacote (não ao arquivo fonte). Isso significa que você deve considerar a estrutura do projeto:
meuprojeto/
├── main.go
└── assets/
├── css/
│ └── style.css
└── js/
└── app.js
//go:embed assets/css/*.css assets/js/*.js
var assetsFS embed.FS
Padrões curinga suportados:
- *: corresponde a arquivos em um único diretório
- **: corresponde a arquivos em subdiretórios recursivamente
Restrições importantes:
- Não é possível incorporar arquivos fora do módulo
- O caminho deve ser uma constante literal (não variáveis)
- Não é possível usar caminhos absolutos
4. Trabalhando com embed.FS – sistema de arquivos virtual
O embed.FS implementa a interface fs.FS, tornando-o compatível com pacotes como net/http e text/template.
Abertura e leitura de arquivos
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed data
var dataFS embed.FS
func main() {
// Lendo um arquivo específico
content, err := dataFS.ReadFile("data/config.json")
if err != nil {
panic(err)
}
// Abrindo para leitura streaming
file, err := dataFS.Open("data/readme.txt")
if err != nil {
panic(err)
}
defer file.Close()
// Listando diretório
entries, _ := dataFS.ReadDir("data")
for _, entry := range entries {
fmt.Println(entry.Name())
}
}
Navegação por subdiretórios
//go:embed all:data
var dataFS embed.FS
func listFiles() {
fs.WalkDir(dataFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Println(path)
return nil
})
}
5. Casos de uso práticos
Servindo arquivos estáticos via HTTP
package main
import (
"embed"
"net/http"
)
//go:embed static
var staticFS embed.FS
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
http.ListenAndServe(":8080", nil)
}
Incorporando templates HTML
package main
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates
var templateFS embed.FS
func main() {
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", nil)
})
http.ListenAndServe(":8080", nil)
}
Empacotando migrações de banco de dados
package main
import (
"embed"
"fmt"
"sort"
)
//go:embed migrations/*.sql
var migrationsFS embed.FS
func runMigrations() error {
entries, err := migrationsFS.ReadDir("migrations")
if err != nil {
return err
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for _, entry := range entries {
sql, _ := migrationsFS.ReadFile("migrations/" + entry.Name())
fmt.Printf("Executando migração: %s\n%s\n", entry.Name(), string(sql))
}
return nil
}
6. Boas práticas e cuidados
Evitando vazamento de arquivos sensíveis
Nunca incorpore arquivos como .env, chaves privadas ou credenciais. Use um .gitignore adequado e seja explícito sobre o que está sendo incorporado:
// ❌ Ruim: incorpora tudo
//go:embed .
// ✅ Bom: explícito
//go:embed public config/templates
Tratamento de erros
Sempre trate erros ao acessar arquivos no embed.FS:
data, err := myFS.ReadFile("config.json")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
// Arquivo não encontrado
}
// Outros erros
}
Diferenças entre desenvolvimento e produção
Em desenvolvimento, você pode usar arquivos do sistema de arquivos real; em produção, os embutidos. Considere usar flags de compilação para alternar:
var tmpl *template.Template
func init() {
if devMode {
tmpl = template.Must(template.ParseGlob("templates/*.html"))
} else {
tmpl = template.Must(template.ParseFS(templateFS, "templates/*.html"))
}
}
7. Comparação com alternativas
| Característica | embed |
go-bindata |
packr |
|---|---|---|---|
| Dependências | Nenhuma | Externa | Externa |
| Suporte nativo | Sim | Não | Não |
| Compressão | Não | Sim | Sim |
| Hot-reload | Não | Não | Sim |
| Manutenção | Oficial | Descontinuado | Ativo |
Vantagens do embed:
- Zero dependências externas
- Suporte oficial da linguagem
- Integração com fs.FS e pacotes padrão
- Simplicidade de uso
Limitações:
- Sem compressão automática
- Sem hot-reload em desenvolvimento
- Não suporta arquivos fora do módulo
8. Exemplo completo e testes
Vamos construir um servidor HTTP completo com assets embutidos:
package main
import (
"embed"
"html/template"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
//go:embed static
var staticFS embed.FS
//go:embed templates
var templateFS embed.FS
func main() {
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", nil)
})
http.ListenAndServe(":8080", mux)
}
func TestServer(t *testing.T) {
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", nil)
})
server := httptest.NewServer(mux)
defer server.Close()
resp, _ := http.Get(server.URL)
if resp.StatusCode != http.StatusOK {
t.Errorf("esperado 200, obtido %d", resp.StatusCode)
}
// Verifica conteúdo
body := make([]byte, 1024)
resp.Body.Read(body)
if !strings.Contains(string(body), "Hello") {
t.Error("conteúdo esperado não encontrado")
}
}
Com embed, seu binário se torna verdadeiramente portátil e autossuficiente, eliminando preocupações com caminhos de arquivos e dependências externas em tempo de execução.
Referências
- Documentação oficial do pacote embed — Referência completa da API e diretiva
//go:embed - Go 1.16 Release Notes - embed — Notas oficiais sobre a introdução do embed na versão 1.16
- Tutorial: Embedding Files in Go — Tutorial oficial da equipe Go sobre incorporação de arquivos
- Using Go's embed directive — Tutorial prático da DigitalOcean com exemplos detalhados
- Embedding Static Files in Go — Guia completo do LogRocket sobre incorporação de arquivos estáticos
- Go embed: The Complete Guide — Guia abrangente com casos de uso avançados e boas práticas
- Go embed vs go-bindata — Comparação detalhada entre embed e ferramentas legadas