Paquetes en Go: imports, visibilidad y cómo organizar el código

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/

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP