El sistema de paquetes de Go es simple pero tiene reglas que el compilador hace cumplir estrictamente: no puedes importar algo sin usarlo, no puede haber ciclos de importación y la visibilidad se controla únicamente con la capitalización del identificador. Esta sencillez hace que los proyectos grandes sean navegables sin herramientas especiales.
La regla de la mayúscula
Un identificador exportado empieza por mayúscula; uno privado, por minúscula. No hay palabras clave public ni private:
package saludo
// Hola es visible fuera del paquete
func Hola(nombre string) string {
return formatear(nombre) // formatear es privada
}
func formatear(nombre string) string {
return "Hola, " + nombre
}
type Persona struct {
Nombre string // exportado
edad int // privado
}
const Version = "1.0.0" // exportada
const interno = "secreto" // privada
Import paths con módulos
El import path de un paquete es la ruta relativa desde la raíz del módulo declarado en go.mod:
// go.mod
// module ejemplo.com/miapp
// go 1.22
// en main.go
import (
"fmt" // paquete de la stdlib
"ejemplo.com/miapp/internal/saludo" // paquete propio
"github.com/gin-gonic/gin" // dependencia externa
)
Alias de importación
Cuando dos paquetes tienen el mismo nombre (caso frecuente con v2), usa un alias:
import (
textjson "encoding/json"
jsonv2 "github.com/go-json-experiment/json"
_ "database/sql/driver" // blank import: solo ejecuta init()
. "math" // dot import: exporta al espacio local (desaconsejado)
)
La función init()
Cada paquete puede tener una o varias funciones init(). Se ejecutan automáticamente cuando el paquete se importa, antes de que main() arranque. No pueden ser llamadas explícitamente:
package config
import "os"
var BaseDatos string
func init() {
BaseDatos = os.Getenv("DB_URL")
if BaseDatos == "" {
BaseDatos = "localhost:5432/dev"
}
}
El blank import _ "net/http/pprof" se usa exclusivamente para provocar el efecto secundario del init() del paquete, que registra los handlers de profiling.
Organizar un proyecto
La estructura más habitual para proyectos de tamaño medio:
miapp/
??? go.mod
??? go.sum
??? cmd/
? ??? servidor/
? ??? main.go ? binario principal
??? internal/
? ??? db/
? ? ??? db.go ? lógica de base de datos (privada)
? ??? config/
? ??? config.go
??? pkg/
??? api/
??? api.go ? paquete reutilizable (público)
El directorio internal/ es especial: el compilador impide que código fuera del módulo lo importe. cmd/ contiene un subdirectorio por binario. pkg/ agrupa paquetes que otros módulos pueden importar.
Por qué Go prohíbe los ciclos de importación
Si el paquete A importa B, y B importa A, Go lo detecta en tiempo de compilación y lo rechaza con:
import cycle not allowed
Esta restricción no es arbitraria: garantiza que el grafo de dependencias es un DAG (grafo acíclico dirigido), lo que permite compilar los paquetes en paralelo y de forma incremental. Cuando aparece un ciclo, generalmente indica que hay que extraer el tipo o la función compartida a un tercer paquete.
Paquete main
Solo los paquetes llamados main con una función main() generan un ejecutable. Un mismo módulo puede tener varios paquetes main en distintos directorios bajo cmd/:
go build ./cmd/servidor/ go build ./cmd/migrador/
