A palavra-chave package dá nome ao pacote a que um ficheiro Go pertence, e tem de ser a primeira linha de código de todos os ficheiros .go. O package main compila para um programa executável, e qualquer outro nome, como package billing, compila para uma biblioteca que outros pacotes importam. Todos os ficheiros de um diretório têm de usar o mesmo nome de pacote, e é esse nome que o restante código escreve antes de um ponto para chegar aos teus nomes exportados, como em billing.Total (especificação do Go).
Resumo rápido
- Todos os ficheiros
.gocomeçam compackage name. Só comentários e linhas em branco podem vir antes. - O
package mainé o programa. Precisa de umafunc main()sem argumentos nem resultados, e ogo buildtransforma-o num binário. Não pode ser importado. - Um diretório é um pacote. Ficheiros com nomes de pacote diferentes na mesma pasta falham com
found packages billing (invoice.go) and payments (tax.go). A principal exceção são os pacotes_testnos ficheiros de teste. - O nome do pacote e o caminho de importação são coisas diferentes. Importas
"gopkg.in/yaml.v3"e chamasyaml.Unmarshal. Por convenção, o nome coincide com o último elemento do caminho. - Os bons nomes de pacote são palavras únicas, curtas e em minúsculas, sem underscores nem mixedCaps. Evita
utilecommon. - A visibilidade decide-se ao nível do pacote. Um nome com maiúscula inicial é exportado para outros pacotes. Um nome em minúsculas é visível em todos os ficheiros do mesmo pacote e em mais nenhum sítio.
O que faz o package main em Go?
O package main diz à toolchain do Go para compilar um executável em vez de uma biblioteca. A função main desse pacote é onde o programa arranca, e quando ela retorna o programa termina. Eis um serviço HTTP completo num só ficheiro:
example.gogopackage main import ( "fmt" "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "ok") }) log.Fatal(http.ListenAndServe(":8080", mux)) }
O go run . compila-o e executa-o, e o go build -o bin/api . escreve um binário. A toolchain impõe as duas regras à volta do main. Um package main sem função main falha na fase de linking:
example.texttextruntime.main_main·f: function main is undeclared in the main package
E executar um pacote de biblioteca com go run ./billing falha antes de qualquer coisa compilar:
example.texttextpackage example.com/shop/billing is not a main package
Como o main é o programa, nenhum outro pacote o pode importar. Se tentares, recebes import "example.com/shop/cmd/api" is a program, not an importable package. Por isso, os projetos Go guardam a lógica em pacotes de biblioteca e mantêm o main pequeno. Um repositório com vários binários põe cada um no seu próprio diretório, normalmente dentro de cmd/, e cada um desses diretórios é um package main separado. O próprio backend do LevelUpGo tem 11, do servidor da API ao importador de conteúdos, e todos partilham 22 pacotes de biblioteca dentro de internal/.
Dois ficheiros no mesmo diretório podem ter nomes de pacote diferentes?
Não. O comando go trata um diretório como um só pacote, por isso todos os ficheiros .go que lá estão têm de declarar o mesmo nome. Imagina que billing/invoice.go começa com package billing e alguém acrescenta billing/tax.go com package payments. O go build ./billing para com:
example.texttextfound packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing
A solução é mudar o nome numa das cláusulas ou mover o ficheiro para um diretório próprio. O diretório é também a unidade mais pequena que podes importar, por isso, se dois ficheiros pertencem a pacotes diferentes, precisam de pastas diferentes.
Já dividir um pacote por vários ficheiros não custa nada. Cada ficheiro do diretório vê os nomes de todos os outros, exportados ou não, sem importar nada. Um pacote billing com invoice.go, tax.go e refund.go comporta-se exatamente como um único ficheiro comprido.
Qual é a diferença entre o nome de um pacote e o caminho de importação?
O caminho de importação é onde o pacote vive, e o nome do pacote é o identificador que usas no código. O caminho de importação é o caminho do módulo em go.mod mais o diretório, como example.com/shop/billing. O nome do pacote vem da cláusula package dos ficheiros desse diretório.
Por convenção, os dois coincidem, e o nome é o último elemento do caminho. A tabela mostra um caminho em que coincidem e as duas exceções habituais, ambas causadas por versões:
| Caminho de importação | Nome do pacote | Porque diferem |
|---|---|---|
net/http | http | Coincidem |
gopkg.in/yaml.v3 | yaml | O caminho tem um sufixo de versão .v3 |
math/rand/v2 | rand | Diretório de versão major /v2 |
github.com/jackc/pgx/v5 | pgx | Diretório de versão major /v5 |
Quando dois imports têm o mesmo nome, dás outro nome a um deles no bloco de imports. Código que cria tokens de sessão e também acrescenta jitter aos retries precisa muitas vezes de crypto/rand e de math/rand/v2, que se chamam ambos rand:
example.gogopackage tokens import ( "crypto/rand" "encoding/hex" mrand "math/rand/v2" "time" ) // NewSessionID returns a random 32-character hex session ID. func NewSessionID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) } // RetryDelay adds up to 100ms of jitter to a base delay. func RetryDelay(base time.Duration) time.Duration { return base + time.Duration(mrand.IntN(100))*time.Millisecond }
O novo nome só se aplica dentro desse ficheiro. Todos os outros ficheiros continuam a ver o nome declarado do pacote. A palavra-chave import em Go cobre os aliases, os imports em branco e as outras formas de importar.
Um diretório pode conter um pacote com um nome diferente do da pasta, e o Go compila-o na mesma. O custo recai sobre quem lê. Quem importa example.com/shop/httpapi-v2 tem de abrir o código-fonte para descobrir que o pacote se chama httpapi. Mantém os dois iguais, a não ser que um sufixo de versão obrigue a uma diferença.
Como dar nome a um pacote Go?
Os nomes de pacotes em Go seguem um pequeno conjunto de regras do Effective Go e do artigo Package names do blog do Go:
- Minúsculas, uma só palavra:
billing,httpapi,ratelimit. Nãorate_limitnemrateLimit. A especificação permite-os, porque um nome de pacote é qualquer identificador, mas o código Go idiomático não os usa. - Curto e específico. O nome é escrito em cada chamada.
strconvé melhor do questringconversion, eauthé melhor do queauthenticationservice. - Com o nome do que oferece.
util,common,helpers,miscemodelsnão dizem nada sobre o que está lá dentro, e acabam por se tornar pacotes que toda a gente importa. Divide-os por finalidade, comomoney,slugifyoupagination. - Não repitas o nome do pacote nos nomes exportados, porque quem chama já o escreve.
http.Serverestá certo ehttp.HTTPServernão.billing.Totalestá certo ebilling.BillingTotalnão. O construtor do tipo principal de um pacote chama-se muitas vezes apenasNew, como emring.New. - Nem uma palavra-chave, nem
_.package typeé um erro de sintaxe epackage _falha cominvalid package name _.
Lê o nome juntamente com os seus identificadores exportados, porque é assim que quem chama o vê. ratelimit.New(100, time.Minute) e config.Load("app.yaml") leem-se como frases. utils.NewRateLimiterUtil não.
Como é que o package controla a visibilidade em Go?
O Go não tem as palavras-chave public, private nem protected. Em vez disso, a fronteira é o pacote. Um nome que começa com letra maiúscula é exportado e fica visível para qualquer pacote que o importe. Um nome em minúsculas não é exportado e só é visível dentro do seu próprio pacote, em todos os ficheiros dele.
example.gogo// Package billing calculates invoice totals and applies VAT. package billing // VATPercent is the standard VAT rate applied to invoices. const VATPercent = 25 // Total returns the sum of line items in cents, including VAT. func Total(lineItems []int64) int64 { var sum int64 for _, c := range lineItems { sum += c } return addVAT(sum) } func addVAT(cents int64) int64 { return cents + cents*VATPercent/100 }
Um handler em package main pode chamar billing.Total, mas billing.addVAT(1000) falha com name addVAT not exported by package billing. A mesma regra aplica-se aos campos e aos métodos das structs. É pela mesma razão que o encoding/json só consegue ver os campos exportados de uma struct.
Para código que vários dos teus pacotes partilham mas que módulos externos não devem importar, o Go tem os diretórios internal. Um pacote em example.com/shop/internal/pricing pode ser importado por tudo o que esteja dentro de example.com/shop, e por mais nada. Outro módulo que tente recebe:
example.texttextuse of internal package example.com/shop/internal/pricing not allowed
O artigo Estrutura de projetos Go explica quando recorrer a internal/ e como organizar cmd/ e os pacotes de biblioteca num serviço real.
O que é um pacote _test em Go?
Os ficheiros de teste, os que terminam em _test.go, podem usar um de dois nomes de pacote. Com package billing, o teste fica dentro do pacote e pode chamar funções não exportadas:
example.gogopackage billing import "testing" func TestAddVAT(t *testing.T) { if got := addVAT(1000); got != 1250 { t.Fatalf("addVAT(1000) = %d, want 1250", got) } }
Com package billing_test, passa a ser um pacote de teste externo. Vive no mesmo diretório, mas tem de importar billing como qualquer outro cliente e só pode usar nomes exportados:
example.gogopackage billing_test import ( "testing" "example.com/shop/billing" ) func TestTotal(t *testing.T) { got := billing.Total([]int64{1000, 2000}) if got != 3750 { t.Fatalf("Total = %d, want 3750", got) } }
Tirando os ficheiros que uma build constraint exclui, como um script gerador com //go:build ignore, o sufixo _test é a única forma de dois nomes de pacote partilharem um diretório. O go test compila os dois pacotes em separado. Os testes externos verificam a API tal como quem chama a usa, e são também a forma como a biblioteca padrão escreve as funções Example executáveis que aparecem na documentação. Muitas bases de código Go misturam os dois tipos, com testes dentro do pacote para os detalhes internos mais delicados e pacotes _test para o comportamento público.
O que pode vir antes da cláusula package?
Só comentários e linhas em branco. Dois tipos de comentário nessa posição significam algo para a toolchain.
Um comentário de documentação do pacote fica logo acima de package, sem linha em branco pelo meio. Por convenção começa com Package billing, e o go doc e o pkg.go.dev mostram-no como a documentação do pacote. Os pacotes grandes costumam pô-lo num ficheiro chamado doc.go que não contém mais nada. Num comando package main, o comentário descreve antes o programa, como em // Api serves the shop's HTTP API.
Uma build constraint decide se o ficheiro é sequer compilado. Tem de vir antes da cláusula package e ser seguida de uma linha em branco:
example.gogo//go:build debug package billing import "log" func init() { log.Println("billing: debug build") }
Este ficheiro só compila com go build -tags debug. Os ficheiros com um sufixo de sistema operativo ou de arquitetura no nome, como watch_linux.go ou asm_arm64.go, recebem o mesmo tratamento a partir do nome, sem comentário nenhum.
O exemplo mostra também o init. Um pacote pode declarar quantas funções init quiser, até várias no mesmo ficheiro. O Go executa-as depois de inicializar as variáveis ao nível do pacote e antes de arrancar qualquer coisa que importe o pacote. Um pacote importado fica totalmente inicializado antes do pacote que o importa, por isso o main corre sempre em último. Reserva o init para configurações baratas, como registar um driver. Tudo o que possa falhar pertence a uma função que devolva um erro.
Onde entra o LevelUpGo
O LevelUpGo ensina Go através de exercícios que executam código Go real no navegador. O curso Packages & Organization cobre nomes exportados, init e estado do pacote, módulos Go e organização de projetos, e o Code Organization mostra como dividir uma base de código em crescimento em pacotes com nomes claros. Se estás a começar, o Go Basics vem primeiro. Para as outras 24 palavras reservadas, vê Palavras-chave do Go: as 25 explicadas.
Perguntas frequentes
O nome de um pacote Go pode ser diferente do nome do diretório?
Sim. O compilador só lê a cláusula package, por isso package httpapi dentro de uma pasta chamada httpapi-v2 compila sem problemas. A convenção é o nome coincidir com o último elemento do caminho de importação, e as principais exceções são os sufixos de versão, como gopkg.in/yaml.v3 (pacote yaml) e math/rand/v2 (pacote rand).
Porque é que um programa Go precisa de package main?
O comando go só compila um executável a partir de um pacote chamado main, e o runtime arranca o programa chamando a função main desse pacote. Todos os outros pacotes são bibliotecas. Um package main sem func main() falha com function main is undeclared in the main package.
Um módulo Go pode ter mais do que um package main?
Sim, um por diretório. Um serviço com um servidor de API e uma ferramenta de migrações tem normalmente cmd/api/main.go e cmd/migrate/main.go, ambos a declarar package main. Compilas ou executas cada um pelo seu diretório: go run ./cmd/api ou go build ./cmd/migrate.
O nome de um pacote Go pode ter underscores ou maiúsculas?
O compilador permite as duas coisas, porque um nome de pacote é qualquer identificador válido exceto _. O estilo do Go diz para não os usares. O artigo Package names do blog do Go pede nomes curtos e em minúsculas, «sem under_scores nem mixedCaps», como strconv ou httputil.
O package é uma palavra-chave em Go?
Sim. O package é uma das 25 palavras-chave reservadas do Go, por isso não o podes usar como nome de variável ou de função. O código Go que precisa de uma variável para um pacote costuma chamar-lhe pkg.
Fontes
- The Go Programming Language Specification, Package clause: https://go.dev/ref/spec#Package_clause
- The Go Programming Language Specification, Program initialization and execution: https://go.dev/ref/spec#Program_initialization_and_execution
- The Go Programming Language Specification, Exported identifiers: https://go.dev/ref/spec#Exported_identifiers
- Effective Go, Package names: https://go.dev/doc/effective_go#package-names
- The Go Blog, Package names: https://go.dev/blog/package-names
- Go Doc Comments, Packages and Commands: https://go.dev/doc/comment#package
- Organizing a Go module: https://go.dev/doc/modules/layout
- Command go, Internal Directories: https://pkg.go.dev/cmd/go#hdr-Internal_Directories
- Command go, Build constraints: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Command go, Test packages: https://pkg.go.dev/cmd/go#hdr-Test_packages
