5. Paquetes y módulos#

5.1. Paquetes#

Un paquete es una colección de archivos que cotienen código fuente, definiciones de constantes, definiciones de tipos, etc. Todos los archivos de un paquete se encuentran en un mismo directorio.

Los paquetes organizan el código en forma lógica. Las variables, funciones y tipos de datos definidos dentro de un paquete son privados del mismo, es decir no se pueden utilizar afuera, a menos que se exporten explicitamente.

Además de que todos los archivos estén en el mismo directorio, cada archivo que forma parte de un paquete debe declarar a que paquete pertenece:

package mipaquete

5.2. Módulos#

A su vez una colección de paquetes pueden conformar un módulo. Un módulo es la unidad fundamental de organización y distribución de código. Los módulos permiten gestionar dependencias, compartir código y versionarlo.

Un módulo se define en una carpeta que contiene un archivo go.mod. En este archivo se especifica el nombre del módulo, la versión de Go con el que se desarrolló y las dependencias de otros módulos.

Todos los paquetes (en general cada paquete en una subcarpeta) que están en la misma carpeta que contiene el archivo go.mod son parte del mismo módulo. En general un módulo corresponde a un proyecto

5.2.1. Gestión de módulos#

Para crear un módulo se usa el comando go init dentro de la carpeta que contendrá el proyecto, seguido del nombre del módulo. Usualmente el nombre del módulo es el repositorio donde se aloja. Ejemplo:

go mod init github.com/untref-ayp2/miproyecto

Este comando creará un nuevo archivo llamado go.mod con el siguiente contenido:

module github.com/untref-ayp2/miproyecto

go 1.24.1

Todos los archivos que se encuentren dentro de la misma carpeta en la que se haya creado go.mod son los que serán considerados parte del módulo github.com/untref-ayp2/miproyecto

miproyecto
├── otropaquete
│   ├── estructuras.go
│   └── estructuras_test.go
├── unpaquete
│   ├── utils.go
│   └── utils_test.go
├── go.mod
├── go.sum
└── main.go

5.2.2. Importar módulos/paquetes#

El objetivo final de organizar nuestro código en paquetes y módulos es que podamos reusarlos en otros proyectos. Cada vez que necesitemos usar una dependencia tanto interna a nuestro módulo como externa, debemos usar la instrucción import, lo que nos permite acceder a las estructuras, funciones y utilidades de los distintos módulos.

En Go exiten distintos tipos de módulos que podemos importar, dependendien de su procedencia. por ejemplo, go provee una serie de módulos que son nativos del lenguaje, como pueden ser:

fmt

entrada y salida por pantalla y teclado

math

constantes y funciones matemáticas

time

funciones para medir y mostrar tiempos

errors

funciones para manipular errores

path

funciones para manipular carpetas y archivos

testing

soporte para test automáticos

strings

funciones para manipular cadenas de caracteres

Hasta ahora hemos utilzado mayormente este tipo de módulos, también podemos utilizar referencias a paquetes dentro de nuestro mismo módulo. Como ya hicimos en un ejemplo anterior.

package main

import ".saludo"

func main() {
    saludo.Saludar()
}

.saludo indica que debemos importar el paquete saludo que está dentro de la misma carpeta desde donde lo importamos ..

Otro tipo de módulos que podemos importar, son los módulos de otros programadores, por ejemplo podríamos pensar en utilizar un supuesto módulo de algoritmos de ordenamiento de la cátedra. Entonces, imaginemos que existe el módulo llamador untref.edu.ar/ayp2/busqueda. Podemos llamarlo de la siguiente manera.

package main

import (
    "fmt"
    "untref.edu.ar/ayp2/busqueda"
)

func main() {
    lista := [10]int{2, 3, 9, 5, 1, 8, 4, -5, -4, 6}
    busqueda.Quicksort(lista)
    fmt.Println(lista)
}

El resultado será:

[-5 -4 1 2 3 4 5 6 8 9]

Hay varios puntos a notar:

  • Siempre se importan paquetes.

  • Cuando importamos un paquete, el texto luego de la última / será el nombre de la referencia que podemos utilizar para acceder a los miembres del paquete. Que además coincide con el nombre del paquete.

  • Podemos renombrar la referencia que se crea al importar un paquete, ya sea por conveniencia como en el caso de que haya un conflicto de nombres entre paquetes de distintos módulos.

    import b "untref.edu.ar/ayp2/busqueda"
    

    Luego podremos utilizar la nueva referencia b en lugar busqueda.

5.3. Dependencias#

Cuando necesitamos utilizar un módulo de terceros, como podría ser un módulo que provee distintos métodos de ordenamieto, primero debemos encontrar el módulo, esto puede ser haciendo una búsqueda en el registro de go que podemos encontrar en https://pkg.go.dev/ o bien, búscando módulos en internet, es posible instalar módulos directamente desde repositorios de Git.

Para instalar y agregar dicha dependencia en nuestro módulo, podemo utilisar la herramienta go get de la siguiente forma:

go get github.com/untref-ayp2/busquedas

Una vez ejecutado este comando, el archivo go.mod, donde se encuentra la definición de nuestro módulo, se verá similar a:

module github.com/untref-ayp2/miproyecto

go 1.24.1

require github.com/untref-ayp2/busquedas v0.1.0

De esta forma, si estamos trabajando con más personas en el mismo proyecto, o si alguien más quiere utilizar nuestro módulo como dependencia en su propio proyecto, será posible respetar los requerimientos que cada módulo en el proyecto necesita que se cumpla.

Si en lugar de instalar y agregar la dependencia con go get, tenemos forma alternativa que es, primero usar la dependencia en nuestro código y luego eejecutar go mod tidy. Veamos un ejemplo.

Su pongamos que creamos un módulo e importamos algunas cosas en nuestro archivo main.go.

Creación de un módulo simple

Primero creamos una carpeta con el nombre mimodulo y nos posicionamos en ella.

mkdir mimodulo && cd mimodulo

Inicializamos el módulo.

$ go mod init github.com/untref-ayp2/mimodulo
go: creating new go.mod: module github.com/untref-ayp2/mimodulo
$ cat go.mod
module github.com/untref-ayp2/mimodulo

go 1.24.1

Usando cualquier método que les resulte conveniente creamos el archivo main.go con el siguiente código.

cat main.go
package main

import (
    "fmt"
    "github.com/fatih/color"
)

func main() {
    msj := color.RedString("¡Texto en rojo!")
    fmt.Println(msj)
}

Ejecutamos nuestro programa, pero vemos que hay un error.

$ go run main.go
main.go:5:2: no required module provides package github.com/fatih/color; to add it:
    go get github.com/fatih/color

Podemos notar que los mensajes de error de Go muchas veces (no siempre) son muy descriptivos y hasta nos pueden sugerir la solución si prestamos especial atención a lo que nos dice.

Si bien en este ejemplo, queremos mostrar una forma alternativa de solucionar la falta de la dependencia, go get es otra forma de arreglar el problema que, intencionalmente, acabamos de crear.

Para ello usaremos go mod tidy, si consultamos la ayuda podemos aprender que es lo que este comando realmente hace con nuestro módulo.

$ go help mod tidy

[...]

Tidy makes sure go.mod matches the source code in the module.
It adds any missing modules necessary to build the current module's
packages and dependencies, and it removes unused modules that
don't provide any relevant packages. It also adds any missing entries
to go.sum and removes any unnecessary ones.

[...]

Entonces, ya sabemos que go mod tidy se va a ocupar de agregar o corregir cualquier tipo de dependencia perdida en nuestro código.

$ go mod tidy
go: finding module for package github.com/fatih/color
go: downloading github.com/fatih/color v1.18.0
go: found github.com/fatih/color in github.com/fatih/color v1.18.0
go: downloading golang.org/x/sys v0.25.0
go: downloading github.com/mattn/go-colorable v0.1.13

Si ahora vemos como luce el archivo go.mod podemos comprobar que se agregó la directiva require donde se declaran las dependencias faltantes (pero adicionalmente go mod tidy también descargó esas dependencias en nuestro proyecto).

$ cat go.mod
module github.com/untref-ayp2/mimodulo

go 1.24.1

require github.com/fatih/color v1.18.0

require (
    github.com/mattn/go-colorable v0.1.13 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    golang.org/x/sys v0.25.0 // indirect
)

Si ahora ejecutamos nuevamente nuestro programa, todo debería funcionar como se espera.

$ go run main.go
¡Texto en rojo!

(si, confien que el texto sale en color rojo)

¡Para practicar!

Se recomienda recrear el ejemplo previamente presentado y ejecutarlo para corroborar que todo funciona correctamente.

5.4. Espacios de trabajo (workspaces)#

Si trabajamos en varios proyectos podemos organizarlos guardándolos a todos juntos dentro de una carpeta raíz, en un espacio de trabajo o workspace. La organización de los módulos dentro de un espacio de trabajo se realiza con el archivo go.work.

Importante

Un espacio de trabajo con varios módulos puede ser útil si trabajamos con varios proyectos vinculados, en caso contrario puede ser mejor tener cada proyecto por separado.

Trabajar dentro de un mismo workspace permite referenciar dependencias entre módulos locales sin necesidad de decargarlos de un registro online.