En Go, la función init()
predeterminada establece una porción de código que debe ejecutarse antes que cualquier otra parte de su paquete. Este código se ejecutará tan pronto como se importe el paquete y puede usarse cuando necesite que su aplicación se inicie en un estado específico; por ejemplo, cuando requiera que la aplicación se inicie con una configuración o un conjunto de recursos específicos. También se utiliza al importar un efecto secundario, una técnica que se utiliza para establecer el estado de un programa al importar un paquete específico. Esto se suele utilizar para registrar
un paquete con otro a fin de garantizar que el programa considere el código correcto para la tarea.
Si bien init()
es una herramienta útil, a veces puede dificultar la lectura del código, dado que una instancia init()
difícil de encontrar afectará en gran medida el orden en el que se ejecuta el código. Debido a esto, es importante que los desarrolladores que comienzan a usar Go comprendan las facetas de esta función, para poder asegurarse de utilizar init()
de forma legible al escribir código.
A través de este tutorial, aprenderá a usar init()
para configurar e inicializar variables de paquetes específicas, cálculos por única vez y registros de un paquete para su uso con otro.
Para algunos de los ejemplos que se incluyen en este artículo, necesitará lo siguiente:
.
├── bin
│
└── src
└── github.com
└── gopherguides
init()
Siempre que declare una función init()
, Go la cargará antes que a cualquier otra cosa de ese paquete. Para demostrarlo, en esta sección se explicará la manera de definir una función init()
y se mostrarán los efectos sobre la forma en que se ejecuta el paquete.
Primero, tomemos lo siguiente como ejemplo de código sin la función init()
:
package main
import "fmt"
var weekday string
func main() {
fmt.Printf("Today is %s", weekday)
}
En este programa, declaramos una variable global llamada weekday
. De forma predeterminada, el valor de weekday
es una cadena vacía.
Ejecutaremos este código:
- go run main.go
Debido a que el valor de weekday
está vacío, al ejecutar el programa, obtendremos el siguiente resultado:
OutputToday is
Podemos completar la variable en blanco introduciendo una función init()
que inicialice el valor de weekday
en el día actual. Añada las siguientes líneas resaltadas a main.go
:
package main
import (
"fmt"
"time"
)
var weekday string
func init() {
weekday = time.Now().Weekday().String()
}
func main() {
fmt.Printf("Today is %s", weekday)
}
En este código, importamos y usamos el paquete time
para obtener el día actual de la semana (Now(). Weekday(). String()
) y, luego, utilizamos init()
para inicializar weekday
con ese valor.
Ahora, cuando ejecutemos el programa, imprimirá el día actual de la semana:
OutputToday is Monday
Aunque esto ilustra la forma en que init()
funciona, es mucho más común usar init()
al importar un paquete. Esto puede ser útil cuando necesita realizar tareas de configuración específicas en un paquete antes de que se utilice. Para demostrarlo, crearemos un programa que requerirá una inicialización específica a fin de que el paquete funcione como se indica.
Primero, escribiremos código para que se seleccione e imprima un animal al azar de un segmento, pero no usaremos init()
en nuestro programa inicial. Esto indicará mejor el problema que tenemos y la manera en que init()
lo resolverá.
Desde su directorio src/github.com/gopherguides/
, cree una carpeta llamada creatrue
con el siguiente comando:
- mkdir creature
Dentro de la carpeta creature
, cree un archivo llamado creature
:
- nano creature/creature.go
En este archivo, añada el siguiente contenido:
package creature
import (
"math/rand"
)
var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
func Random() string {
i := rand.Intn(len(creatures))
return creatures[i]
}
Este archivo define una variable llamada creatures
que tiene un conjunto de animales marinos que se inicializan como valores. También tiene una función Random
exportada que mostrará un valor al azar de la variable creatures
.
Guarde y cierre este archivo.
A continuación, crearemos un paquete cmd
que usaremos para escribir nuestra función main()
e invocar el paquete creature
.
En el mismo nivel de archivo desde el que creamos la carpeta creature
, cree una carpeta cmd
con el siguiente comando:
- mkdir cmd
Dentro de la carpeta cmd
, cree un archivo llamado main.go
:
- nano cmd/main.go
Añada el siguiente contenido al archivo:
package main
import (
"fmt"
"github.com/gopherguides/creature"
)
func main() {
fmt.Println(creature.Random())
fmt.Println(creature.Random())
fmt.Println(creature.Random())
fmt.Println(creature.Random())
}
Aquí, importamos el paquete creature
y luego, en la función main()
, usamos la función creature.Random()
para obtener un animal al azar e imprimirlo cuatro veces.
Guarde y cierre main.go
.
Ahora, tenemos todo nuestro programa escrito. Sin embargo, para poder ejecutar este programa, también debemos crear algunos archivos de configuración a fin de que nuestro código funcione correctamente. Go utiliza Go Modules para configurar las dependencias de paquetes e importar recursos. Estos módulos son archivos de configuración que se disponen en su directorio de paquetes e indican al compilador el punto desde el cual se deben importar los paquetes. Si bien en este artículo no obtendrá información sobre los módulos, podemos escribir algunas líneas de configuración para que este ejemplo funcione a nivel local.
En el directorio cmd
, cree un archivo llamado go.mod
:
- nano cmd/go.mod
Una vez que el archivo esté abierto, disponga el siguiente contenido:
module github.com/gopherguides/cmd
replace github.com/gopherguides/creature => ../creature
La primera línea de este archivo indica al compilador que el paquete cmd
que creamos es, de hecho, github.com/gopherguides/cmd
. La segunda línea indica al compilador que github.com/gopherguides/creature
se encuentra a nivel local en el disco, en el directorio ../creature
.
Guarde y cierre el archivo. A continuación, cree un archivo go.mod
en el directorio creature
:
- nano creature/go.mod
Añada la siguiente línea de código al archivo:
module github.com/gopherguides/creature
Esto indica al compilador que el paquete creature
que creamos, en realidad, es el paquete github.com/gopherguides/creature
. Sin esto, el paquete cmd
no tendría registro del punto desde el cual debería importar este paquete.
Guarde y cierre el archivo.
Ahora, debería contar con esta estructura de directorios y distribución de archivos:
├── cmd
│ ├── go.mod
│ └── main.go
└── creature
├── go.mod
└── creature.go
Ahora que completamos toda la configuración, podemos ejecutar el programa main
con el siguiente comando:
- go run cmd/main.go
Esto proporcionará lo siguiente:
Outputjellyfish
squid
squid
dolphin
Cuando ejecutamos este programa, recibimos cuatro valores y los imprimimos. Si ejecutamos el programa varias veces, observaremos que siempre obtenemos el mismo resultado, en vez de un resultado al azar como se espera. Esto se debe a que el paquete rand
crea números pseudoaleatorios que generarán de forma sistemática el mismo resultado para un único estado inicial. Para lograr un número más aleatorio, podemos propagar el paquete o establecer un origen cambiante para que el estado inicial sea diferente cada vez que ejecutemos el programa. En Go, es habitual usar la hora actual para propagar el paquete rand
.
Dado que queremos que el paquete creature
maneje la funcionalidad aleatoria, abra este archivo:
- nano creature/creature.go
Añada las siguientes líneas al archivo creature.go
:
package creature
import (
"math/rand"
"time"
)
var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
func Random() string {
rand.Seed(time.Now().UnixNano())
i := rand.Intn(len(creatures))
return creatures[i]
}
En este código, importamos el paquete time
y usamos Seed()
para propagar la hora actual. Guarde el archivo y ciérrelo.
Ahora, cuando ejecutemos el programa, obtendremos un resultado aleatorio:
- go run cmd/main.go
Outputjellyfish
octopus
shark
jellyfish
Si continúa ejecutando el programa una y otra vez, seguirá obteniendo resultados aleatorios. Sin embargo, esta todavía no es una implementación ideal de nuestro código, porque cada vez qye se invoca creature.Random()
también se vuelve a propagar el paquete rand
invocando rand.Seed(time.Now(). UnixNano())
de nuevo. La repetición de la propagación aumentará la probabilidad de realizar la propagación con el mismo valor inicial si el reloj interno no se ha modificado, lo cual posiblemente generará repeticiones del patrón aleatorio o aumentará el tiempo de procesamiento de la CPU al hacer que el programa espere el cambio del reloj.
Para solucionar esto, podemos usar una función init()
. Actualizaremos el archivo creature.go
:
- nano creature/creature.go
Añada las siguientes líneas de código:
package creature
import (
"math/rand"
"time"
)
var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
func init() {
rand.Seed(time.Now().UnixNano())
}
func Random() string {
i := rand.Intn(len(creatures))
return creatures[i]
}
La adición de la función init()
indica al compilador que, al importar el paquete creature
, debe ejecutar la función init()
una vez con una sola propagación para la generación de un número al azar. Esto garantiza que no ejecutemos código más de lo necesario. Ahora, si ejecutamos el programa, continuaremos obteniendo resultados aleatorios:
- go run cmd/main.go
Outputdolphin
squid
dolphin
octopus
En esta sección, vimos que el uso de init()
puede garantizar que se realicen inicializaciones o cálculos adecuados antes de usar un paquete. A continuación, veremos la forma de usar varias instrucciones init()
en un paquete.
init()
A diferencia de la función main()
, que solo se puede declarar una vez, la función init()
puede declararse varias veces en un paquete. Sin embargo, varias funciones init()
pueden hacer que resulte difícil determinar la que tiene prioridad sobre las demás. En esta sección, se mostrará la manera de mantener el control sobre varias instrucciones init()
.
En la mayoría de los casos, las funciones init()
se ejecutarán en el orden en el que las encuentre. Tomemos el siguiente código como ejemplo:
package main
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func init() {
fmt.Println("Third init")
}
func init() {
fmt.Println("Fourth init")
}
func main() {}
Si ejecutamos el programa con el siguiente comando:
- go run main.go
Obtendremos el siguiente resultado:
OutputFirst init
Second init
Third init
Fourth init
Observe que cada función init()
se ejecuta en el orden en el que el compilador la encuentra. Sin embargo, es posible que no siempre sea tan fácil determinar el orden de invocación de las funciones init()
.
Veamos una estructura de paquetes más complicada en la que tenemos varios archivos, cada uno con su propia función init()
declarada en su interior. Para ilustrar esto, crearemos un programa que comparta una variable llamada message
y la imprima.
Elimine los directorios creature
y cmd
y su contenido de la sección anterior, y sustitúyalos por los directorios y la estructura de archivos que se indican a continuación:
├── cmd
│ ├── a.go
│ ├── b.go
│ └── main.go
└── message
└── message.go
Ahora, agregaremos el contenido de cada archivo. En a.go
, añada las siguientes líneas:
package main
import (
"fmt"
"github.com/gopherguides/message"
)
func init() {
fmt.Println("a ->", message.Message)
}
Este archivo contiene una función init()
única que imprime el valor de message.Message
del paquete message
.
A continuación, añada el siguiente contenido a b.go
:
package main
import (
"fmt"
"github.com/gopherguides/message"
)
func init() {
message.Message = "Hello"
fmt.Println("b ->", message.Message)
}
En b.go
, hay una función init()
única que fija el valor de message.Message
en Hello
y lo imprime.
A continuación, cree main.go
para que tenga el siguiente aspecto:
package main
func main() {}
Este archivo no hace más que simplemente ofrecer un punto de entrada para que se ejecute el programa.
Por último, cree su archivo message.go
de la siguiente manera:
package message
var Message string
Nuestro paquete messages
declara la variable Message
exportada.
Para iniciar el programa, ejecute el siguiente comando desde el directorio cmd:
- go run *.go
Debido a que hay varios archivos de Go en la carpeta cmd
que conforman el paquete main
, debemos indicar al compilador que todos los archivos .go
de la carpeta cmd
deben compilarse. Usar *.go
indica al compilador que cargue todos los archivos que terminan en .go
de la carpeta cmd
. Si emitiéramos el comando go main.go
, el programa no se compilaría porque no detectaría el código en los archivos a.go
y b.go
.
Esto generará el siguiente resultado:
Outputa ->
b -> Hello
De acuerdo con la especificación del lenguaje de Go para Inicialización de paquetes, cuando se encuentran varios archivos en un paquete se procesan en orden alfabético. Es por esto que la primera vez que imprimimos message.Message
desde a.go
, el valor estaba vacío. El valor no se inicializó hasta que se ejecutó la función init()
desde b.go
.
Si cambiáramos el nombre del archivo de a.go
a c.go
, obtendríamos un resultado diferente:
Outputb -> Hello
a -> Hello
Ahora, el compilador encuentra b.go
primero y, por lo tanto, el valor de message.Message
ya está inicializado con Hello
cuando se encuentra la función init()
en c.go
.
Este comportamiento podría generar un problema en su código. En el ámbito del desarrollo de software, es habitual cambiar los nombres de los archivos y, por la forma en que se procesa init()
, hacer este cambio puede modificar el orden en el que se procesa init()
. Esto podría tener el efecto no deseado de cambiar el resultado de su programa. Para garantizar un comportamiento de inicialización reproducible, se recomienda que los sistemas de compilación presenten a un compilador varios archivos pertenecientes al mismo paquete en orden de nombre de archivo léxico. Una forma de garantizar que se carguen todas las funciones init()
en orden es declararlas en su totalidad en un único archivo. Esto impedirá que el orden cambie, incluso si se cambian los nombres de los archivos.
Además de garantizar que el orden de sus funciones init()
no cambie, también debe intentar evitar la administración del estado en su paquete usando variables globales; es decir, variables accesibles desde cualquier punto del paquete. En el programa anterior, la variable message.Message
estaba disponible para todo el paquete y mantuvo el estado del programa. Debido a este acceso, las instrucciones init()
pudieron cambiar la variable y desestabilizar la previsibilidad de su programa. Para evitar esto, intente trabajar con variables en espacios controlados que tengan el menor nivel de acceso posible y, al mismo tiempo, permitan que el programa funcione.
Vimos que puede tener varias declaraciones init()
en un único paquete. Sin embargo, esto puede crear efectos no deseados y hacer que su programa sea difícil de leer o predecir. Evitar tener varias instrucciones init()
o mantenerlas en un único archivo garantizará que el comportamiento de su programa no cambie al mover los archivos o modificar su nombre.
A continuación, veremos cómo se utiliza init()
para la importación con efectos secundarios.
init()
para efectos secundariosEn Go, a veces es conveniente importar un paquete no por su contenido, sino por los efectos secundarios que se producen al importarlo. Esto suele significar que hay una instrucción init()
en el código importado que se ejecuta antes de cualquier otro código, lo cual permite que el desarrollador manipule el estado en el que se inicia el programa. La técnica se denomina importación para efectos secundarios.
Un caso de uso común para realizar una importación para obtener efectos secundarios tiene que ver con registrar la funcionalidad en su código, lo cual permite que un paquete registre la parte del código que necesita usar su programa. En el paquete image
, por ejemplo, la función image.Decode
debe registrar el formato de la imagen que intenta decodificar (jpg
, png
y gif
, entre otros) para poder ejecutarse. Puede realizar esto importando, primero, un programa específico que tenga un efecto secundario de instrucción init()
.
Supongamos que intenta usar image.Decode
en un archivo .png
con el siguiente fragmento de código:
. . .
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
. . .
De todos modos, se compilará un programa con este código, pero cada vez que intentemos decodificar una imagen png
, obtendremos un error.
Para solucionar esto, primero, debemos registrar un formato de imagen para image.Decode
. Afortunadamente, el paquete image/png
contiene la siguiente instrucción init()
:
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
Por lo tanto, si importamos image/png
en nuestro fragmento de decodificación, la función image.RegisterFormat()
en image/png
se ejecutará antes que cualquier parte de nuestro código:
. . .
import _ "image/png"
. . .
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
Con esto, se establecerá el estado y se registrará que necesitamos la versión png
de image.Decode()
. El registro se realizará como efecto secundario de la importación de image/png
.
Posiblemente, haya observado el identificador en blanco (_
) antes de "image/png"
. Esto es necesario porque Go no le permite importar paquetes que no se utilicen en todo el programa. Cuando se incluye el identificador en blanco, el valor de la importación se descarta para que solo se produzca el efecto secundario de la importación. Esto significa que, aunque nunca invoquemos el paquete image/png
en nuestro código, podemos importarlo para obtener el efecto secundario.
Es importante conocer el momento en que se debe importar un paquete debido a su efecto secundario. Sin el registro adecuado, es probable que su programa se compile y no funcione correctamente cuando se ejecute. Los paquetes de la biblioteca estándar declararán la necesidad de este tipo de importación en su documentación. Si escribe un paquete que requiere una importación para obtener efectos secundarios, también debe asegurarse de que la instrucción init()
que esté usando se documente para que los usuarios que importen su paquete puedan utilizarlo correctamente.
A través de este tutorial, aprendió que la función
init() se carga antes que el resto del código de su paquete y que puede realizar tareas específicas para un paquete, como inicializar un estado deseado. También aprendió que el orden en el que el compilador ejecuta varias instrucciones init()
depende del orden en el que carga los archivos de origen. Si desea obtener más información sobre init()
, consulte la documentación oficial de Golang o lea los comentarios acerca de la función en la comunidad de Go.
Puede leer más sobre funciones en nuestro artículo Cómo definir e invocar funciones en Go o consultar toda la serie Cómo programar en Go.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!