Voltar ao blog

A palavra-chave package em Go: package main, regras de nomes e exemplos

Todos os ficheiros Go começam com uma cláusula package. Vê o que faz o package main, como os nomes de pacote se ligam aos diretórios e aos caminhos de importação, e as regras de nomes do Go.

A palavra-chave package em Go: package main, regras de nomes e exemplos

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 .go começam com package name. Só comentários e linhas em branco podem vir antes.
  • O package main é o programa. Precisa de uma func main() sem argumentos nem resultados, e o go build transforma-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 _test nos ficheiros de teste.
  • O nome do pacote e o caminho de importação são coisas diferentes. Importas "gopkg.in/yaml.v3" e chamas yaml.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 util e common.
  • 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.gogo
package 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.texttext
runtime.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.texttext
package 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.texttext
found 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çãoNome do pacotePorque diferem
net/httphttpCoincidem
gopkg.in/yaml.v3yamlO caminho tem um sufixo de versão .v3
math/rand/v2randDiretório de versão major /v2
github.com/jackc/pgx/v5pgxDiretó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.gogo
package 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ão rate_limit nem rateLimit. 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 que stringconversion, e auth é melhor do que authenticationservice.
  • Com o nome do que oferece. util, common, helpers, misc e models não dizem nada sobre o que está lá dentro, e acabam por se tornar pacotes que toda a gente importa. Divide-os por finalidade, como money, slugify ou pagination.
  • Não repitas o nome do pacote nos nomes exportados, porque quem chama já o escreve. http.Server está certo e http.HTTPServer não. billing.Total está certo e billing.BillingTotal não. O construtor do tipo principal de um pacote chama-se muitas vezes apenas New, como em ring.New.
  • Nem uma palavra-chave, nem _. package type é um erro de sintaxe e package _ falha com invalid 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.texttext
use 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.gogo
package 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.gogo
package 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

Escreve Go como um engenheiro sénior

Lições interativas no navegador. As primeiras são grátis.

Experimenta uma lição grátisOu cria uma conta gratuita