Volver al blog

La palabra clave package en Go: package main, reglas de nombres y ejemplos

Todo archivo de Go empieza con una cláusula package. Aprende qué hace package main, cómo se relacionan los nombres de paquete con los directorios y las rutas de importación, y las reglas de nombres de Go.

La palabra clave package en Go: package main, reglas de nombres y ejemplos

La palabra clave package indica el paquete al que pertenece un archivo fuente de Go, y tiene que ser la primera línea de código de todo archivo .go. package main genera un programa ejecutable, y cualquier otro nombre, como package billing, genera una biblioteca que otros paquetes importan. Todos los archivos de un directorio deben usar el mismo nombre de paquete, y ese nombre es lo que el resto del código escribe antes del punto para acceder a tus nombres exportados, como en billing.Total (especificación de Go).

Resumen rápido

  • Todo archivo .go empieza con package name. Antes solo pueden ir comentarios y líneas en blanco.
  • package main es el programa. Necesita una func main() sin argumentos ni resultados, y go build la convierte en un binario. No se puede importar.
  • Un directorio es un paquete. Si hay archivos con nombres de paquete distintos en la misma carpeta, la compilación falla con found packages billing (invoice.go) and payments (tax.go). La principal excepción son los paquetes _test de los archivos de test.
  • El nombre del paquete y la ruta de importación son cosas distintas. Importas "gopkg.in/yaml.v3" y llamas a yaml.Unmarshal. Por convención, el nombre coincide con el último elemento de la ruta.
  • Un buen nombre de paquete es una sola palabra corta y en minúsculas, sin guiones bajos ni mixedCaps. Evita util y common.
  • La visibilidad se decide por paquete. Un nombre que empieza con mayúscula se exporta a otros paquetes. Un nombre en minúscula es visible para todos los archivos del mismo paquete y para nadie más.

¿Qué hace package main en Go?

package main le dice a la toolchain de Go que genere un ejecutable en lugar de una biblioteca. La función main de ese paquete es donde arranca el programa, y cuando retorna, el programa termina. Este es un servicio HTTP completo en un solo archivo:

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))
}

go run . lo compila y lo ejecuta, y go build -o bin/api . escribe un binario. La toolchain impone las dos reglas sobre main. Un package main sin función main falla al enlazar:

example.texttext
runtime.main_main·f: function main is undeclared in the main package

Y ejecutar un paquete de biblioteca con go run ./billing falla antes de compilar nada:

example.texttext
package example.com/shop/billing is not a main package

Como main es el programa, ningún otro paquete puede importarlo. Si lo intentas, obtienes import "example.com/shop/cmd/api" is a program, not an importable package. Por eso los proyectos en Go guardan su lógica en paquetes de biblioteca y dejan main pequeño. Un repositorio con varios binarios pone cada uno en su propio directorio, normalmente bajo cmd/, y cada uno de esos directorios es un package main independiente. El propio backend de LevelUpGo tiene 11, desde el servidor de la API hasta el importador de contenido, y todos comparten 22 paquetes de biblioteca bajo internal/.

¿Pueden dos archivos del mismo directorio tener nombres de paquete distintos?

No. El comando go trata un directorio como un solo paquete, así que todos los archivos .go que contiene deben declarar el mismo nombre. Supón que billing/invoice.go empieza con package billing y alguien añade billing/tax.go con package payments. go build ./billing se detiene con:

example.texttext
found packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing

La solución es renombrar una de las cláusulas o mover el archivo a su propio directorio. El directorio es además la unidad más pequeña que puedes importar, así que si dos archivos pertenecen a paquetes distintos, necesitan carpetas distintas.

Repartir un paquete en varios archivos, en cambio, no cuesta nada. Cada archivo del directorio ve los nombres de todos los demás, exportados o no, sin importar nada. Un paquete billing con invoice.go, tax.go y refund.go se comporta exactamente igual que un único archivo largo.

¿Qué diferencia hay entre el nombre de un paquete y su ruta de importación?

La ruta de importación es dónde vive el paquete, y el nombre del paquete es el identificador que usas en el código. La ruta de importación es la ruta del módulo en go.mod más el directorio, como example.com/shop/billing. El nombre del paquete sale de la cláusula package de los archivos de ese directorio.

Por convención coinciden, así que el nombre es el último elemento de la ruta. La tabla muestra una ruta en la que coinciden y las dos excepciones habituales, ambas causadas por versiones:

Ruta de importaciónNombre del paquetePor qué difieren
net/httphttpCoinciden
gopkg.in/yaml.v3yamlLa ruta lleva el sufijo de versión .v3
math/rand/v2randDirectorio de versión mayor /v2
github.com/jackc/pgx/v5pgxDirectorio de versión mayor /v5

Cuando dos imports comparten nombre, renombras uno en el bloque de imports. El código que crea tokens de sesión y además añade jitter a los reintentos suele necesitar tanto crypto/rand como math/rand/v2, y los dos se llaman 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
}

El renombrado solo se aplica dentro de ese archivo. Los demás archivos siguen viendo el nombre declarado del paquete. La palabra clave import en Go cubre los alias, los imports en blanco y las demás formas de importar.

Un directorio puede contener un paquete cuyo nombre no coincida con el de la carpeta, y Go lo compilará. El coste lo pagan quienes leen el código. Quien importa example.com/shop/httpapi-v2 tiene que abrir el código fuente para saber que el paquete se llama httpapi. Mantén ambos iguales salvo que un sufijo de versión obligue a lo contrario.

¿Cómo deberías nombrar un paquete de Go?

Los nombres de paquete en Go siguen unas pocas reglas de Effective Go y del artículo Package names del blog de Go:

  • En minúsculas y de una sola palabra: billing, httpapi, ratelimit. No rate_limit ni rateLimit. La especificación los permite porque un nombre de paquete puede ser cualquier identificador, pero el código Go idiomático no los usa.
  • Cortos y específicos. El nombre se escribe en cada llamada. strconv es mejor que stringconversion, y auth es mejor que authenticationservice.
  • Nombrados por lo que ofrecen. util, common, helpers, misc y models no dicen nada de lo que hay dentro, y acaban convertidos en paquetes que importa todo el mundo. Divídelos por propósito, como money, slugify o pagination.
  • No repitas el nombre del paquete en los nombres exportados, porque quien llama ya lo escribe. http.Server es correcto y http.HTTPServer no. billing.Total es correcto y billing.BillingTotal no. El constructor del tipo principal de un paquete suele llamarse simplemente New, como en ring.New.
  • Ni una palabra clave ni _. package type es un error de sintaxis y package _ falla con invalid package name _.

Lee el nombre junto con sus identificadores exportados, porque así es como lo ve cada llamada. ratelimit.New(100, time.Minute) y config.Load("app.yaml") se leen como frases. utils.NewRateLimiterUtil, no.

¿Cómo controla package la visibilidad en Go?

Go no tiene palabras clave public, private ni protected. El límite lo marca el paquete. Un nombre que empieza con mayúscula se exporta y es visible para cualquier paquete que lo importe. Un nombre en minúscula no se exporta y solo es visible dentro de su propio paquete, en todos sus archivos.

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
}

Un handler en package main puede llamar a billing.Total, pero billing.addVAT(1000) falla con name addVAT not exported by package billing. La misma regla se aplica a los campos y métodos de un struct. Por eso encoding/json solo puede ver los campos exportados de un struct.

Para el código que comparten varios de tus paquetes pero que los módulos externos no deberían importar, Go tiene los directorios internal. Un paquete en example.com/shop/internal/pricing lo puede importar cualquier cosa que cuelgue de example.com/shop, y nada más. Otro módulo que lo intente obtiene:

example.texttext
use of internal package example.com/shop/internal/pricing not allowed

Estructura de proyectos en Go explica cuándo recurrir a internal/ y cómo organizar cmd/ y los paquetes de biblioteca en un servicio real.

¿Qué es un paquete _test en Go?

Los archivos de test, los que terminan en _test.go, pueden usar uno de dos nombres de paquete. package billing pone el test dentro del paquete, así que puede llamar a funciones no 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)
	}
}

package billing_test lo convierte en un paquete de test externo. Vive en el mismo directorio, pero tiene que importar billing como cualquier otro cliente y solo puede usar nombres 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)
	}
}

Aparte de los archivos que excluye una restricción de compilación, como un script generador con //go:build ignore, el sufijo _test es la única forma de que dos nombres de paquete compartan directorio. go test compila los dos paquetes por separado. Los tests externos comprueban la API tal como la usan los clientes, y también son la forma en que la biblioteca estándar escribe las funciones Example ejecutables que aparecen en la documentación. Muchos proyectos en Go combinan ambos tipos: tests dentro del paquete para los detalles internos delicados y paquetes _test para el comportamiento público.

¿Qué puede ir antes de la cláusula package?

Solo comentarios y líneas en blanco. Dos tipos de comentario en esa posición significan algo para la toolchain.

Un comentario de documentación del paquete va justo encima de package, sin ninguna línea en blanco entre medias. Por convención empieza con Package billing, y go doc y pkg.go.dev lo muestran como la documentación del paquete. Los paquetes grandes suelen ponerlo en un archivo llamado doc.go que no contiene nada más. En un comando package main, el comentario describe el programa, como en // Api serves the shop's HTTP API.

Una restricción de compilación (build constraint) decide si el archivo se compila o no. Tiene que ir antes de la cláusula package y seguida de una línea en blanco:

example.gogo
//go:build debug

package billing

import "log"

func init() {
	log.Println("billing: debug build")
}

Este archivo solo se compila con go build -tags debug. Los archivos cuyo nombre lleva un sufijo de sistema operativo o de arquitectura, como watch_linux.go o asm_arm64.go, reciben el mismo trato a partir del nombre, sin ningún comentario.

El ejemplo también muestra init. Un paquete puede declarar cualquier número de funciones init, incluso varias en un mismo archivo. Go las ejecuta después de inicializar las variables a nivel de paquete y antes de que arranque cualquier paquete que lo importe. Un paquete importado se inicializa por completo antes que el paquete que lo importa, así que main siempre se ejecuta el último. Reserva init para una configuración barata, como registrar un driver. Todo lo que pueda fallar debe ir en una función que devuelva un error.

Dónde encaja LevelUpGo

LevelUpGo enseña Go con ejercicios que ejecutan código Go real en el navegador. El curso Packages & Organization cubre los nombres exportados, init y el estado del paquete, los módulos de Go y la estructura de proyectos, y Code Organization muestra cómo dividir en paquetes con nombres claros un código que no para de crecer. Si estás empezando, Go Basics va primero. Para las otras 24 palabras reservadas, consulta Palabras clave de Go: las 25 explicadas.

Preguntas frecuentes

¿Puede el nombre de un paquete de Go ser distinto del nombre de su directorio?

Sí. El compilador solo lee la cláusula package, así que package httpapi dentro de una carpeta llamada httpapi-v2 compila sin problemas. La convención es que el nombre coincida con el último elemento de la ruta de importación, y las principales excepciones son los sufijos de versión como gopkg.in/yaml.v3 (paquete yaml) y math/rand/v2 (paquete rand).

¿Por qué un programa en Go necesita package main?

El comando go solo genera un ejecutable a partir de un paquete llamado main, y el runtime arranca el programa llamando a la función main de ese paquete. Todos los demás paquetes son bibliotecas. Un package main sin func main() falla con function main is undeclared in the main package.

¿Puede un módulo de Go tener más de un package main?

Sí, uno por directorio. Un servicio con un servidor de API y una herramienta de migraciones suele tener cmd/api/main.go y cmd/migrate/main.go, y ambos declaran package main. Cada uno se compila o se ejecuta por su directorio: go run ./cmd/api o go build ./cmd/migrate.

¿Puede el nombre de un paquete de Go llevar guiones bajos o mayúsculas?

El compilador permite ambas cosas, porque un nombre de paquete puede ser cualquier identificador válido excepto _. El estilo de Go dice que no se usen. El artículo Package names del blog de Go pide nombres cortos y en minúsculas, «sin under_scores ni mixedCaps», como strconv o httputil.

¿Es package una palabra clave en Go?

Sí. package es una de las 25 palabras clave reservadas de Go, así que no puedes usarla como nombre de variable ni de función. El código Go que necesita una variable para un paquete suele llamarla pkg.

Fuentes

Escribe Go como un ingeniero sénior

Lecciones interactivas en tu navegador. Las primeras son gratis.

Prueba una lección gratisO crea una cuenta gratuita