estructura-programa-go-paquetes-main

Estructura de un programa en Go: paquetes e imports

  • 5 min

Un programa en Go es un conjunto de paquetes que organizan el código y sus dependencias, empezando normalmente por un paquete main cuando queremos generar un ejecutable.

En el artículo anterior hicimos nuestro “Hola Mundo”. Funcionó, vimos el texto en la consola, y nos sentimos muy bien. Pero, siendo sinceros, copiamos y pegamos sin entender del todo la estructura que estábamos usando.

Si vienes de C#, Java o C++, estarás acostumbrado a ver namespaces, clases, métodos estáticos o distintas variantes de main. En Go la cosa cambia: la organización gira alrededor de paquetes.

Vamos a diseccionar la anatomía de un programa en Go para entender los paquetes, las importaciones y la función main.

Entender esto ahora nos ahorrará muchos dolores de cabeza después. Go es muy obstinado (opinionated) Zen cómo debemos organizar el código.

Los paquetes

La primera declaración de un archivo .go indica a qué paquete pertenece (antes puede haber comentarios o directivas de compilación).

package nombrepaquete
Copied!

En Go, el paquete es la unidad básica de organización del código. No existen las clases como contenedores de código estático. Toda declaración pertenece a un paquete.

Paquete main frente a paquetes reutilizables

Aquí hay una distinción crítica que debes memorizar:

  1. package main: es especial. Le indica al compilador que este paquete puede generar un programa ejecutable. Debe contener una función main() que será el punto de entrada.
  2. Cualquier otro nombre: si el paquete se llama calculadora, utils o modelos, Go lo tratará como un paquete reutilizable y no generará un ejecutable por sí mismo.

La regla del directorio

Go tiene una norma muy sencilla para organizar el código:

Todos los archivos .go dentro de una misma carpeta deben pertenecer al mismo paquete.

No puedes tener en la carpeta /utils un archivo que diga package utils y otro que diga package tools. El compilador te lanzará un error. Esto fuerza a que la estructura de carpetas de tu proyecto refleje la estructura lógica de tu código.

Las importaciones

Justo debajo de la declaración del paquete, indicamos qué otras librerías necesitamos usar.

En el “Hola Mundo” usamos import "fmt". En un programa real necesitaremos más importaciones. Go nos permite agruparlas para que el código quede más limpio usando un bloque entre paréntesis:

import (
    "fmt"
    "math"
    "time"
)
Copied!

La “obsesión” de Go con la limpieza

Si vienes de otros lenguajes, seguramente tengas la costumbre de dejar imports “por si acaso” o que tu IDE los añada y se te olvide borrarlos.

En Go eso es un error de compilación.

Si importas "math" pero no usas ninguna función matemática en tu código, el programa no compilará.

package main

import "fmt"
import "math" // ERROR: imported and not used: "math"

func main() {
    fmt.Println("Hola")
}
Copied!

¿Por qué son tan estrictos? Porque los imports no usados ralentizan la compilación y ensucian el código. Go quiere que tu código sea rápido de compilar y fácil de leer. Lo que no está, no se usa.

Visibilidad: exportado frente a no exportado

En C# o Java usamos palabras clave como public, private o protected para controlar quién puede acceder a una variable o función.

En Go no existen esas palabras clave. La visibilidad fuera del paquete se define por la primera letra del nombre:

  • Mayúscula: el identificador está exportado y puede usarse desde otros paquetes.
  • Minúscula: el identificador no está exportado y solo puede usarse dentro del mismo paquete.

Ejemplo:

package miPaquete

// FuncionPublica puede ser llamada desde otro archivo o paquete
func FuncionPublica() {
    // Implementación pública
}

// funcionInterna solo puede llamarse desde dentro de miPaquete
func funcionInterna() {
    // Implementación interna
}
Copied!

Esto se aplica a funciones, variables, constantes, tipos y campos de estructuras. Una mayúscula inicial nos indica de un vistazo que el identificador está exportado.

Por eso usamos fmt.Println y no fmt.println. La función Println es pública dentro del paquete fmt.

La función main

Finalmente, tenemos el punto de entrada. La función main es donde comienza la ejecución de nuestro programa ejecutable.

func main() {
    // Tu código empieza aquí
}
Copied!

Tiene un par de características que la diferencian de C o C++:

  1. No tiene argumentos: No recibe argc ni argv directamente. (Para leer argumentos de consola se usa el paquete os, lo veremos más adelante).
  2. No retorna valor: No devuelve un int o código de estado. Si el programa termina bien, el sistema asume éxito (código 0). Si queremos salir con error, debemos llamar explícitamente a os.Exit(1).

El ciclo de vida

Cuando ejecutamos el programa, Go inicializa primero los paquetes importados y después el paquete main. Dentro de cada paquete se preparan sus variables y se ejecutan sus funciones init() antes de continuar:

  1. Se inicializan los paquetes de los que depende main.
  2. Se inicializan las variables del paquete main.
  3. Se ejecutan sus funciones init() (si existen).
  4. Se ejecuta main().
  5. Cuando main() termina, el programa finaliza aunque queden otras goroutines en ejecución.

Ejemplo completo integrando conceptos

Vamos a ver un ejemplo un poco más elaborado que un “Hola Mundo” para ver todo esto en acción.

Imagínate este archivo:

package main // 1. Paquete ejecutable

import ( // 2. Imports agrupados
	"fmt"
	"math/rand/v2" // Generación de números pseudoaleatorios
)

// Una variable a nivel de paquete (accesible en todo el archivo)
var nombrePrograma = "Generador Aleatorio"

func main() { // 3. Punto de entrada
	fmt.Println("Iniciando:", nombrePrograma)

	numero := generarNumero()

	fmt.Println("El número elegido es:", numero)
}

// Esta función empieza por minúscula.
// Es PRIVADA. Solo se puede usar en este paquete.
func generarNumero() int {
	return rand.IntN(100) // IntN está exportada por el paquete rand
}
Copied!

Ahora que entendemos el esqueleto, en el próximo artículo vamos a rellenarlo de carne: vamos a ver Variables, Constantes y los Tipos de Datos básicos en Go. ¡Seguimos!