Volver al blog

La palabra clave import en Go: sintaxis, alias e imports en blanco y con punto

Cómo funciona la palabra clave import en Go: imports agrupados, rutas de importación, por qué un import sin usar rompe la compilación, alias, imports en blanco y con punto, y cómo resolver ciclos de importación.

La palabra clave import en Go: sintaxis, alias e imports en blanco y con punto

La palabra clave import hace que los nombres exportados de otro paquete estén disponibles en un archivo fuente de Go. Los imports van justo después de la cláusula package y antes de cualquier otra declaración, y cada uno identifica un paquete por su ruta de importación, como "net/http" o "github.com/jackc/pgx/v5". Después accedes al contenido de ese paquete a través de su nombre, como en http.ListenAndServe. Un import también puede llevar un alias, el identificador en blanco _ o un punto ., y cada forma cambia cómo se enlaza el nombre del paquete en el archivo (especificación de Go).

Resumen rápido

  • Los imports van después de package y antes de todo lo demás. La mayoría de los archivos usan un único bloque agrupado import ( ... ).
  • Una ruta de importación es una cadena. Las rutas de la biblioteca estándar son cortas ("encoding/json"), y todas las demás empiezan con una ruta de módulo de go.mod.
  • Un import sin usar es un error de compilación: "os" imported and not used. goimports y gopls añaden y quitan imports por ti.
  • alias "path" renombra un paquete dentro de un solo archivo. Úsalo cuando dos imports comparten nombre, como html/template y text/template.
  • _ "path" importa un paquete solo por sus efectos secundarios, que se ejecutan en sus funciones init. Los drivers de base de datos y net/http/pprof funcionan así.
  • . "path" trae los nombres al archivo sin calificador. Las guías de estilo lo desaconsejan y staticcheck lo marca como ST1001, salvo en algunos casos raros de tests.
  • Go rechaza los ciclos de importación. La solución es un nuevo paquete compartido o una interfaz definida donde se usa.
  • Los imports tienen ámbito de archivo. Cada archivo de un paquete importa lo que usa, aunque otro archivo del mismo paquete ya importe ese paquete.

¿Cómo se importa un paquete en Go?

Escribe import seguido de la ruta del paquete entre comillas dobles. Un archivo que necesita un solo paquete puede usar una sola línea, y un archivo que necesita varios usa un bloque agrupado entre paréntesis:

example.gogo
package main

import "fmt"

func main() {
	fmt.Println("ok")
}
example.gogo
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type healthResponse struct {
	Status string `json:"status"`
}

func main() {
	http.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(healthResponse{Status: "ok"})
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Las dos formas significan lo mismo, y casi todo el código Go usa el bloque agrupado. Los imports siempre van después de la cláusula package y antes de cualquier const, var, type o func. Si pones uno más abajo, el parser se detiene con syntax error: imports must appear before other declarations. Tampoco hay forma de importar dentro de una función.

Las herramientas de formato ordenan el bloque por ti. gofmt ordena las líneas de cada grupo por ruta de importación. goimports, que la mayoría de los editores ejecutan al guardar, además divide el bloque en dos grupos separados por una línea en blanco, con la biblioteca estándar primero y todo lo demás después:

example.gogo
import (
	"context"
	"fmt"
	"net/http"

	"example.com/shop/billing"
	"github.com/jackc/pgx/v5"
)

El orden no afecta al programa. Mantiene los diffs pequeños y permite ver de un vistazo qué dependencias vienen de fuera de la biblioteca estándar.

¿Qué es una ruta de importación en Go?

Una ruta de importación es la cadena que le dice al comando go dónde vive un paquete. Los paquetes de la biblioteca estándar tienen rutas cortas sin punto en el primer elemento, como "fmt", "net/http" o "crypto/rand". La ruta de cualquier otro paquete empieza con una ruta de módulo.

Tus propios paquetes usan la ruta de módulo declarada en go.mod más el directorio. Con module example.com/shop en go.mod, el paquete de ./billing se importa como "example.com/shop/billing". En modo módulo no existen los imports relativos como "./billing", así que un paquete local siempre se importa por su ruta completa.

Los paquetes de terceros funcionan igual, y el comando go los resuelve a través de go.mod:

  1. go get github.com/jackc/pgx/v5 descarga el módulo y añade una línea require a go.mod.
  2. Escribes "github.com/jackc/pgx/v5" en el bloque de imports.
  3. Más adelante, go mod tidy añade cualquier módulo que necesiten tus imports y quita los que ya nadie importa.

El último elemento de la ruta suele ser el nombre del paquete, pero no siempre. "github.com/jackc/pgx/v5" ofrece un paquete llamado pgx, y "gopkg.in/yaml.v3" ofrece yaml. La palabra clave package en Go explica cómo se relacionan los nombres y las rutas.

¿Por qué Go se niega a compilar imports sin usar?

Go trata un import sin usar como un error, no como una advertencia. Si dejas "os" en un archivo que ya no lo llama, go build se detiene:

example.texttext
./main.go:5:2: "os" imported and not used

Las preguntas frecuentes de Go explican el motivo. Un import sin usar ralentiza la compilación y añade una dependencia que el programa no necesita, y en un proyecto grande esas dependencias se acumulan. Las advertencias se acaban ignorando, así que Go lo trata como un error. go mod tidy hace la misma limpieza en go.mod y quita los requisitos que nada importa.

En la práctica, rara vez los arreglas a mano. goimports y gopls, el servidor de lenguaje de Go que usan VS Code y el soporte de Go de GoLand, borran los imports sin usar y añaden los que faltan al guardar. Si estás depurando y quieres conservar un import un momento, asigna algo de ese paquete al identificador en blanco, como en var _ = os.Exit, y quita la línea antes de hacer commit.

¿Cómo se renombra un import en Go?

Pon un alias antes de la ruta. El alias sustituye el nombre del paquete solo en ese archivo:

example.gogo
import htmltemplate "html/template"

El motivo habitual es un choque de nombres. html/template y text/template se llaman los dos template, y un servicio de correo suele necesitar ambos: texto plano para el asunto y salida con escape HTML para el cuerpo. Importar los dos sin alias falla con template redeclared in this block. Un alias en uno de ellos lo soluciona:

example.gogo
package main

import (
	htmltemplate "html/template"
	"os"
	"text/template"
)

func main() {
	subject := template.Must(template.New("subject").Parse("Your invoice {{.ID}}\n"))
	body := htmltemplate.Must(htmltemplate.New("body").Parse("<p>{{.Note}}</p>\n"))

	data := map[string]string{"ID": "INV-42", "Note": "<script>alert(1)</script>"}
	subject.Execute(os.Stdout, data)
	body.Execute(os.Stdout, data)
}

La salida muestra por qué existen dos paquetes separados. El asunto se imprime como Your invoice INV-42, y el cuerpo como <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>, con el escape que aplica html/template.

Los alias también aparecen cuando muchos paquetes comparten un último elemento genérico. El código de Kubernetes importa varios paquetes que se llaman v1, así que escribe corev1 "k8s.io/api/core/v1" y metav1 "k8s.io/apimachinery/pkg/apis/meta/v1". Si no hay choque, conserva el nombre real. Todo el mundo reconoce http., pero con un alias inventado quien lee tiene que volver al bloque de imports para saber qué significa.

¿Qué hace un import en blanco _ en Go?

Un import en blanco carga un paquete sin enlazar su nombre. No puedes llamar a nada de él, pero el paquete se inicializa igualmente, así que sus variables a nivel de paquete reciben su valor y sus funciones init se ejecutan. Algunos paquetes hacen su trabajo útil en init, donde se registran en otro paquete.

El ejemplo más habitual es un driver de database/sql:

example.gogo
package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
	db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
}

El paquete stdlib de pgx llama a sql.Register("pgx", ...) en su init. Tu código solo habla con database/sql, pero sin el import en blanco el driver nunca se registra y sql.Open devuelve sql: unknown driver "pgx" (forgotten import?).

Otros imports en blanco que te encontrarás en servicios reales:

ImportQué hace su init
_ "github.com/jackc/pgx/v5/stdlib"Registra el driver pgx en database/sql
_ "net/http/pprof"Añade los handlers de profiling /debug/pprof/ a http.DefaultServeMux
_ "image/png"Registra el decodificador PNG para que image.Decode pueda leer archivos PNG
_ "time/tzdata"Incrusta la base de datos de zonas horarias para contenedores que no la tienen
_ "embed"Nada en tiempo de ejecución. Es obligatorio para que //go:embed funcione sobre una variable string o []byte

El caso de embed es una regla de la toolchain, no un efecto secundario de init. Si omites el import, la compilación falla con go:embed requires import "embed" (or import _ "embed", if package is not used).

El orden de inicialización es predecible. Cada paquete importado se inicializa por completo, incluidos sus propios imports, antes que el paquete que lo importa. Desde Go 1.21, la especificación también fija el orden entre paquetes que no dependen entre sí. Se inicializan en el orden de sus rutas de importación. Dentro de un paquete, primero reciben su valor las variables a nivel de paquete y después se ejecutan las funciones init en el orden en que los archivos llegan al compilador, que el comando go ordena por nombre de archivo. main se ejecuta el último. Un paquete que importan varios otros se inicializa una sola vez.

Pon los imports en blanco en main o en el paquete que realmente necesita el efecto secundario, no en una biblioteca que otros importan. Una biblioteca que importa en blanco net/http/pprof expone endpoints de profiling en todos los programas que la usan, los quieran o no.

¿Deberías usar imports con punto en Go?

Casi nunca. Un import con punto mezcla los nombres exportados de un paquete con los del archivo, así que los llamas sin el nombre del paquete:

example.gogo
import . "strings"

func normalizeEmail(email string) string {
	return ToLower(TrimSpace(email))
}

En una función de tres líneas se sigue sin problema. En un archivo largo, quien ve ToLower no sabe si está definida en este paquete ni de qué import viene. La página Go Code Review Comments lo desaconseja, y staticcheck lo marca:

example.texttext
main.go:3:8: should not use dot imports (ST1001)

El único uso aceptado es un test que tiene que vivir fuera del paquete que prueba por culpa de un ciclo. Code Review Comments pone el ejemplo de package foo_test importando bar/testutil, que a su vez importa foo. Un import con punto de foo hace que ese test se lea como si estuviera dentro del paquete. Algunos frameworks de test, como Ginkgo y Gomega, también documentan imports con punto para sus matchers. Fuera de esos casos, escribe el nombre del paquete.

¿Qué es un ciclo de importación en Go?

Un ciclo de importación son dos o más paquetes que se importan entre sí, directamente o a través de una cadena. Go los prohíbe, así que si billing importa customers y customers importa billing, la compilación falla:

example.texttext
package example.com/shop/billing
	imports example.com/shop/customers from billing.go
	imports example.com/shop/billing from customers.go: import cycle not allowed

La regla mantiene bien definido el orden de inicialización y hace que la compilación sea rápida, porque el compilador siempre puede compilar un paquete después de todo aquello de lo que depende. Además, dice algo sobre el diseño: un ciclo suele indicar que dos paquetes son en realidad uno, o que ambos dependen de algo que debería estar en un tercero.

Hay dos soluciones habituales:

  1. Mueve la parte compartida a su propio paquete. Si billing y customers necesitan un tipo CustomerID, ponlo en un paquete pequeño shop/ids o shop/domain que no importe a ninguno de los dos.
  2. Define una interfaz donde se usa. Si billing solo necesita consultar el email de un cliente, puede declarar lo que necesita y dejar que main le pase la implementación real:
example.gogo
package billing

import "context"

// CustomerLookup is the one thing billing needs from the customers package.
type CustomerLookup interface {
	Email(ctx context.Context, customerID string) (string, error)
}

type Service struct {
	customers CustomerLookup
}

func NewService(customers CustomerLookup) *Service {
	return &Service{customers: customers}
}

Ahora billing ya no importa customers, y customers puede importar billing sin problema. Cualquier tipo con un método Email compatible satisface la interfaz sin nombrarla, así que la dependencia va en una sola dirección.

El comando go aplica una regla relacionada a package main. Ningún paquete puede importarlo, y si lo intentas obtienes import "example.com/shop/cmd/api" is a program, not an importable package.

¿Los imports de Go tienen ámbito de archivo o de paquete?

Los imports tienen ámbito de archivo. Un import solo enlaza el nombre del paquete en el archivo que lo contiene, aunque el resto de nombres a nivel de paquete se compartan entre todos los archivos del paquete. Aquí server.go importa log:

example.gogo
package main

import "log"

func main() {
	log.Println("server starting")
	runJobs()
}

Y jobs.go, en el mismo paquete, llama a log sin importarlo:

example.gogo
package main

func runJobs() {
	log.Println("running jobs")
}

runJobs es visible en server.go porque las funciones a nivel de paquete se comparten. El import de log no, así que la compilación falla:

example.texttext
./jobs.go:4:2: undefined: log

Cada archivo lista sus propios imports, y goimports los añade por ti. Esto también significa que el nombre de un import puede chocar con una declaración a nivel de paquete de otro archivo. Si declaras var log = ... en un archivo mientras otro archivo importa log, obtienes log already declared through import of package log ("log").

Los alias también tienen ámbito de archivo. Renombrar mrand "math/rand/v2" en un archivo no afecta a los demás archivos del paquete.

Go también limita qué paquetes puedes importar. Un paquete dentro de un directorio internal/ solo lo puede importar el código que cuelga del directorio padre de internal. La palabra clave package en Go cubre internal/ y cómo funciona la visibilidad entre paquetes.

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 las dependencias, y los comandos de módulos que usas a diario, como go get y go mod tidy. Si estás empezando, Go Basics va primero. Para las otras 24 palabras reservadas, consulta Palabras clave de Go: las 25 explicadas.

Preguntas frecuentes

¿Es import una palabra clave en Go?

Sí. import es una de las 25 palabras clave reservadas de Go, así que no puedes usarla como nombre de variable, de función ni de paquete. Solo puede aparecer en las declaraciones de import al principio de un archivo.

¿Se puede importar un paquete dentro de una función en Go?

No. Los imports solo se permiten en el nivel superior de un archivo, justo después de la cláusula package. Un import dentro del cuerpo de una función falla con syntax error: unexpected keyword import. Si solo necesitas un paquete en una función, impórtalo igualmente al principio del archivo.

¿Cómo se importa un paquete local en Go?

Usa la ruta de módulo de go.mod seguida del directorio del paquete. En un módulo llamado example.com/shop, el paquete de ./internal/pricing se importa como "example.com/shop/internal/pricing". Los módulos de Go no admiten imports relativos como "./pricing".

¿Qué diferencia hay entre import _ e import . en Go?

import _ "path" ejecuta la inicialización del paquete, pero no te da forma de referirte a él. Sirve para efectos secundarios como registrar un driver de base de datos. import . "path" hace lo contrario y pone todos los nombres exportados del paquete directamente en tu archivo, de modo que puedes llamarlos sin calificador. Los imports en blanco son habituales en Go idiomático, mientras que los imports con punto se desaconsejan.

¿Importa el orden de los imports en Go?

No. Al compilador no le importa el orden de las líneas en un bloque de imports, y el orden de inicialización lo determina el grafo de dependencias, no el orden en que listas los imports. gofmt los ordena por ruta y goimports separa la biblioteca estándar de los demás paquetes, pero eso es solo por legibilidad.

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