Golang 05 — Pacotes em Go
- Golang – 01. Introdução
- Golang – 02. Tipos Básicos
- Golang – 03. Structs e Funções e Métodos
- Golang – 04. Estruturas de Controle
- Golang 05 — Pacotes em Go
- Golang 06 — Módulos e dependências
- Golang – 06. Biblioteca Padrão I – fmt e strings
- Golang – 07. Biblioteca Padrão II – os, os/exec e os/user
- Golang 08 — Interface
- Golang 09 — Goroutines – Concorrência e Paralelismo
- Golang – 10. Goroutines – Canais
- Golang – 11. Pacotes e Documentação no Go
Índice
- 1. Uso de pacotes
- 1.1. Importando múltiplos pacotes
- 1.2. Papel do import
- 1.3. Observação
- 2. O que é um pacote
- 2.1. Pacotes além do main
- 2.2. Organização por diretório
- 2.3. Subdiretórios
- 2.4. Observação
- 2.5. Regra prática (refinada)
- 3. Criando um pacote próprio
- 3.1. Estrutura do pacote
- 3.2. Usando o pacote
- 3.3. Inicializando o módulo
- 3.4. Execução
- 3.5. O que está acontecendo
- 3.6. Observação
- 4. Exportação
- 4.1. Evoluindo o mathlib
- 4.2. Funções internas
- 4.3. Separação de responsabilidades
- 4.4. Regra prática
- 5. Organização do código
- 5.1. Divisão por responsabilidade
- 5.2. Unidade de compilação
- 5.3. Crescimento do pacote
- 5.4. Observação
- 6. Conclusão
No universo de Go, os pacotes são o principal mecanismo de organização e reutilização de código. Ao longo desta série, já utilizamos pacotes da biblioteca padrão — como fmt, math e time — por meio da palavra-chave import, incorporando funcionalidades prontas diretamente em nossos programas.
Esse uso, embora comum, representa apenas uma parte do modelo de organização da linguagem.
Um pacote em Go não é apenas uma coleção de funções utilitárias. Ele define um limite claro de responsabilidade, agrupando tipos, funções e comportamentos relacionados em uma unidade coesa. Essa estrutura permite que o código seja dividido em partes menores, mais fáceis de entender, manter e reutilizar.
Neste artigo, o foco é justamente esse: compreender como o Go organiza código através de pacotes. A partir dos exemplos já utilizados, vamos explorar:
- o papel dos pacotes na estrutura de um programa
- como criar pacotes próprios
- como reutilizar código entre diferentes partes de um projeto
Sem entrar ainda em detalhes de versionamento ou distribuição, o objetivo aqui é estabelecer uma base sólida para organização de código em Go, mantendo o mesmo fluxo prático adotado nos artigos anteriores.
Uso de pacotes
Até aqui, os programas utilizaram funcionalidades da biblioteca padrão por meio da palavra-chave import. Um exemplo recorrente é o uso do pacote fmt:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}Nesse trecho, fmt.Println é uma função definida no pacote fmt. A forma de acesso segue um padrão simples:
nome_do_pacote.Nome
Esse modelo evita conflitos de nomes e torna explícita a origem de cada funcionalidade utilizada no código.
Importando múltiplos pacotes
Quando mais de um pacote é necessário, a declaração pode ser agrupada:
import (
"fmt"
"math"
)O uso continua direto:
fmt.Println(math.Sqrt(16))
Papel do import
A declaração import não apenas torna funções disponíveis, mas também define dependências explícitas do programa.
Se um pacote é importado e não utilizado, o compilador gera erro. Esse comportamento força o código a permanecer limpo e evita dependências desnecessárias.
Observação
Neste ponto, o uso de pacotes ainda é direto: importar e utilizar.
Nos próximos tópicos, vamos sair desse cenário e começar a organizar código próprio em pacotes, aplicando o mesmo modelo já utilizado com a biblioteca padrão.
O que é um pacote
Em Go, um pacote é um conjunto de arquivos .go que pertencem ao mesmo diretório e são compilados juntos.
Todo arquivo começa com uma declaração package, que define a qual pacote aquele código pertence:
package main
Nos exemplos anteriores, utilizamos sempre o pacote main. Esse pacote tem um papel específico:
ele define um programa executável
Ou seja, apenas códigos dentro de package main podem ser executados diretamente.
Pacotes além do main
Nem todo código precisa ser executável. Na prática, a maior parte do código é organizada em pacotes reutilizáveis.
Por exemplo:
package calc
Esse código não será executado diretamente. Ele define funcionalidades que podem ser utilizadas por outros programas.
Organização por diretório
A relação entre pacote e diretório é direta:
calc/ ├── sum.go ├── sub.go
Se ambos os arquivos começarem com:
package calc
eles pertencem ao mesmo pacote.
Subdiretórios
Subdiretórios são utilizados para organizar o projeto, e cada diretório define um pacote distinto do ponto de vista de importação.
Por exemplo:
calc/ ├── sum.go ├── vector/ │ └── vector.go
Mesmo que os arquivos tenham a mesma declaração:
package calc
eles pertencem a pacotes diferentes:
calccalc/vector
A distinção ocorre pelo caminho de importação, não apenas pelo nome do pacote.
Observação
Na prática, é comum manter o mesmo nome de pacote em diferentes diretórios quando esses diretórios fazem parte de um mesmo contexto lógico.
Isso permite organizar o código fisicamente sem necessariamente alterar a forma como ele é referenciado internamente.
No entanto, para o compilador, cada diretório continua sendo um pacote independente.
Regra prática (refinada)
- um diretório → um pacote (unidade de compilação)
- o nome do pacote pode se repetir em diferentes diretórios
- o que diferencia pacotes é o caminho de importação
Criando um pacote próprio
Até aqui, utilizamos apenas pacotes da biblioteca padrão. O próximo passo é organizar código próprio da mesma forma.
Para isso, vamos construir um pacote simples chamado mathlib.
Estrutura do pacote
Crie um diretório para o pacote:
mathlib/ ├── vector.go
Dentro de vector.go:
package mathlib
type Vector struct {
X float64
Y float64
Z float64
}
func (v Vector) Add(u Vector) (r Vector) {
r.X = v.X + u.X
r.Y = v.Y + u.Y
r.Z = v.Z + u.Z
return
}Esse arquivo define um pacote chamado mathlib com uma estrutura (Vector) e um método (Add).
Usando o pacote
Agora, em outro diretório, podemos utilizar esse código:
project/ ├── mathlib/ │ └── vector.go └── main.go
Conteúdo de main.go:
package main
import (
"fmt"
"project/mathlib"
)
func main() {
a := mathlib.Vector{X: -1, Y: -1, Z: 0}
b := mathlib.Vector{X: 3, Y: -2, Z: 1}
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("a+b:", a.Add(b))
}Inicializando o módulo
Na raiz do projeto:
$ go mod init project
Execução
$ go run . a: -1i + -1j b: 3i + -2j + 1k a+b: 2i + -3j + 1k
O que está acontecendo
- o diretório
mathlibdefine um pacote reutilizável - o programa principal importa esse pacote
- estruturas e métodos são utilizados como qualquer pacote da biblioteca padrão
Observação
Note que nomes como Vector e Add começam com letra maiúscula.
Esse detalhe define o que pode ser acessado fora do pacote — e será explorado no próximo tópico.
Exportação
Em Go, a visibilidade de funções, tipos e variáveis é definida pela capitalização do nome.
Identificadores iniciados com letra maiúscula são exportados e podem ser acessados fora do pacote. Identificadores iniciados com letra minúscula são restritos ao pacote onde foram definidos.
Esse mecanismo define, na prática, o que o pacote expõe como API.
Evoluindo o mathlib
No exemplo anterior, o pacote mathlib continha apenas operações com vetores. Podemos estender esse pacote adicionando novas funcionalidades.
Um exemplo é a resolução de polinômios de segundo grau. Para isso, adicionamos um novo arquivo ao pacote:
mathlib/ ├── vector.go └── poly.go
Conteúdo de poly.go:
package mathlib
import (
"fmt"
"math"
)
func SolvePoly2(a, b, c float64) (x1, x2 float64, err error) {
d := delta2(a, b, c)
if d < 0 {
return 0, 0, fmt.Errorf("there are no real roots")
}
rd := math.Sqrt(d)
x1 = (-b + rd) / (2 * a)
x2 = (-b - rd) / (2 * a)
return
}Agora o pacote mathlib passa a oferecer uma nova funcionalidade, sem alterar a estrutura existente.
Funções internas
A função SolvePoly2 utiliza um cálculo intermediário (delta). Esse cálculo não precisa ser exposto fora do pacote.
Podemos mantê-lo como uma função interna:
func delta2(a, b, c float64) float64 {
return math.Pow(b, 2) - 4*a*c
}Separação de responsabilidades
Nesse ponto, o pacote passa a ter dois níveis claros:
- funções exportadas, que definem o comportamento do pacote
- funções internas, que implementam esse comportamento
Do lado de fora do pacote, apenas SolvePoly2 está disponível:
x1, x2, err := mathlib.SolvePoly2(1, -3, 2)
A função delta2 não faz parte da interface pública e não pode ser utilizada diretamente.
Regra prática
- exporte apenas o que representa comportamento do pacote
- mantenha internas as funções auxiliares
- evite expor detalhes de implementação
Organização do código
À medida que o pacote evolui, novas funcionalidades podem ser adicionadas sem alterar a forma como ele é utilizado.
No exemplo anterior, o pacote mathlib passou a conter mais de um arquivo:
mathlib/ ├── vector.go └── poly.go
Ambos os arquivos começam com:
package mathlib
Isso indica que pertencem ao mesmo pacote, mesmo estando separados fisicamente.
Divisão por responsabilidade
A separação em múltiplos arquivos não altera o comportamento do pacote, mas melhora a organização.
vector.goconcentra operações com vetorespoly.gotrata de polinômios
Essa divisão permite:
- localizar código com mais facilidade
- manter cada arquivo focado em um conjunto de responsabilidades
- evoluir partes do pacote sem interferir diretamente nas demais
Unidade de compilação
Mesmo separados, os arquivos são compilados juntos.
Para o compilador, o pacote mathlib é uma única unidade.
Crescimento do pacote
À medida que o código cresce, essa organização passa a ser essencial.
Novas funcionalidades podem ser adicionadas criando novos arquivos dentro do mesmo pacote, mantendo o mesmo padrão:
mathlib/ ├── vector.go ├── poly.go └── matrix.go
Observação
A organização em arquivos não define a visibilidade dos elementos.
A visibilidade continua sendo controlada pela capitalização dos nomes, como visto anteriormente.
Conclusão
Neste artigo, os pacotes foram tratados como unidade central de organização em Go.
Partindo do uso de pacotes da biblioteca padrão, avançamos para a criação de código próprio, mantendo o mesmo modelo de uso: importar e utilizar. Ao longo do processo, alguns pontos se consolidam:
- um pacote agrupa código relacionado em uma unidade coesa
- a estrutura física (diretórios e arquivos) define essa organização
- a API do pacote é definida explicitamente pela exportação
- detalhes de implementação podem ser mantidos internos
Esse modelo permite evoluir o código de forma incremental, adicionando novas funcionalidades sem alterar o uso externo do pacote.
Na prática, isso reduz acoplamento e mantém a interface do pacote estável, mesmo quando sua implementação interna muda.
A partir deste ponto, o próximo passo natural é entender como esses pacotes são organizados em módulos e como o Go gerencia dependências entre eles.
Deixe uma resposta