introduction
Dans Go, la fonction prédéfinieinit()
déclenche un morceau de code à exécuter avant toute autre partie de votre package. Ce code s'exécutera dès que lespackage is imported et peut être utilisé lorsque vous avez besoin de votre application pour s'initialiser dans un état spécifique, par exemple lorsque vous avez une configuration spécifique ou un ensemble de ressources avec lequel votre application doit démarrer. Il est également utilisé lorsqueimporting a side effect, une technique utilisée pour définir l'état d'un programme en important un package spécifique. Ceci est souvent utilisé pourregister
un package avec un autre pour s'assurer que le programme considère le code correct pour la tâche.
Bien queinit()
soit un outil utile, il peut parfois rendre le code difficile à lire, car une instance deinit()
difficile à trouver affectera considérablement l'ordre dans lequel le code est exécuté. Pour cette raison, il est important que les développeurs novices dans Go comprennent les facettes de cette fonction, afin de s'assurer d'utiliserinit()
de manière lisible lors de l'écriture du code.
Dans ce didacticiel, vous apprendrez commentinit()
est utilisé pour la configuration et l’initialisation de variables de package spécifiques, les calculs ponctuels et l’enregistrement d’un package à utiliser avec un autre package.
Conditions préalables
Pour certains exemples de cet article, vous aurez besoin des éléments suivants:
-
Un espace de travail Go configuré en suivantHow To Install Go and Set Up a Local Programming Environment. Ce tutoriel utilisera la structure de fichier suivante:
.
├── bin
│
└── src
└── github.com
└── gopherguides
Déclaration deinit()
Chaque fois que vous déclarez une fonctioninit()
, Go la chargera et l'exécutera avant tout autre élément de ce package. Pour illustrer cela, cette section explique comment définir une fonctioninit()
et montre les effets sur le fonctionnement du package.
Prenons d'abord ce qui suit comme exemple de code sans la fonctioninit()
:
main.go
package main
import "fmt"
var weekday string
func main() {
fmt.Printf("Today is %s", weekday)
}
Dans ce programme, nous avons déclaré unvariable global appeléweekday
. Par défaut, la valeur deweekday
est une chaîne vide.
Lançons ce code:
go run main.go
Comme la valeur deweekday
est vide, lorsque nous exécutons le programme, nous obtiendrons la sortie suivante:
OutputToday is
Nous pouvons remplir la variable vide en introduisant une fonctioninit()
qui initialise la valeur deweekday
au jour courant. Ajoutez les lignes en surbrillance suivantes àmain.go
:
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)
}
Dans ce code, nous avons importé et utilisé le packagetime
pour obtenir le jour actuel de la semaine (Now().Weekday().String()
), puis utiliséinit()
pour initialiserweekday
avec cette valeur.
Maintenant, lorsque nous exécuterons le programme, il affichera le jour de la semaine en cours:
OutputToday is Monday
Bien que cela illustre le fonctionnement deinit()
, un cas d'utilisation beaucoup plus typique pourinit()
est de l'utiliser lors de l'importation d'un package. Cela peut être utile lorsque vous devez effectuer des tâches d’installation spécifiques dans un package avant de l’utiliser. Pour illustrer cela, créons un programme nécessitant une initialisation spécifique pour que le package fonctionne comme prévu.
Initialisation des packages à l'importation
Tout d'abord, nous allons écrire un code qui sélectionne une créature aléatoire à partir d'unslice et l'imprime. Cependant, nous n'utiliserons pasinit()
dans notre programme initial. Cela montrera mieux le problème que nous avons et commentinit()
résoudra notre problème.
À partir de votre répertoiresrc/github.com/gopherguides/
, créez un dossier appelécreature
avec la commande suivante:
mkdir creature
Dans le dossiercreature
, créez un fichier appelécreature.go
:
nano creature/creature.go
Dans ce fichier, ajoutez le contenu suivant:
creature.go
package creature
import (
"math/rand"
)
var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
func Random() string {
i := rand.Intn(len(creatures))
return creatures[i]
}
Ce fichier définit une variable appeléecreatures
qui a un ensemble de créatures marines initialisées en tant que valeurs. Il a également une fonctionexportedRandom
qui retournera une valeur aléatoire à partir de la variablecreatures
.
Enregistrez et quittez ce fichier.
Ensuite, créons un packagecmd
que nous utiliserons pour écrire notre fonctionmain()
et appeler le packagecreature
.
Au même niveau de fichier à partir duquel nous avons créé le dossiercreature
, créez un dossiercmd
avec la commande suivante:
mkdir cmd
Dans le dossiercmd
, créez un fichier appelémain.go
:
nano cmd/main.go
Ajoutez le contenu suivant au fichier:
cmd/main.go
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())
}
Ici, nous avons importé le packagecreature
, puis dans la fonctionmain()
, nous avons utilisé la fonctioncreature.Random()
pour récupérer une créature aléatoire et l'imprimer quatre fois.
Enregistrez et quittezmain.go
.
Nous avons maintenant tout notre programme écrit. Cependant, avant de pouvoir exécuter ce programme, nous devons également créer quelques fichiers de configuration pour que notre code fonctionne correctement. Go utiliseGo Modules pour configurer les dépendances de package pour l'importation de ressources. Ces modules sont des fichiers de configuration placés dans votre répertoire de packages qui indiquent au compilateur d'où importer les packages. Bien que l’apprentissage des modules dépasse le cadre de cet article, nous ne pouvons écrire que quelques lignes de configuration pour que cet exemple fonctionne localement.
Dans le répertoirecmd
, créez un fichier nommégo.mod
:
nano cmd/go.mod
Une fois le fichier ouvert, placez-le dans le contenu suivant:
cmd/go.mod
module github.com/gopherguides/cmd
replace github.com/gopherguides/creature => ../creature
La première ligne de ce fichier indique au compilateur que le packagecmd
que nous avons créé est en faitgithub.com/gopherguides/cmd
. La deuxième ligne indique au compilateur quegithub.com/gopherguides/creature
peut être trouvé localement sur le disque dans le répertoire../creature
.
Enregistrez et fermez le fichier. Ensuite, créez un fichiergo.mod
dans le répertoirecreature
:
nano creature/go.mod
Ajoutez la ligne de code suivante au fichier:
creature/go.mod
module github.com/gopherguides/creature
Cela indique au compilateur que le packagecreature
que nous avons créé est en fait le packagegithub.com/gopherguides/creature
. Sans cela, le packagecmd
ne saurait pas d'où importer ce package.
Enregistrez et quittez le fichier.
Vous devriez maintenant avoir la structure de répertoire et la disposition de fichier suivants:
├── cmd
│ ├── go.mod
│ └── main.go
└── creature
├── go.mod
└── creature.go
Maintenant que toute la configuration est terminée, nous pouvons exécuter le programmemain
avec la commande suivante:
go run cmd/main.go
Cela donnera:
Outputjellyfish
squid
squid
dolphin
Lorsque nous avons exécuté ce programme, nous avons reçu quatre valeurs et les avons imprimées. Si nous exécutons le programme plusieurs fois, nous remarquerons que nousalways obtenons la même sortie, plutôt qu'un résultat aléatoire comme prévu. Cela est dû au fait que le packagerand
crée des nombres pseudo-aléatoires qui généreront systématiquement la même sortie pour un seul état initial. Pour obtenir un nombre plus aléatoire, nous pouvonsseed le paquet, ou définir une source changeante de sorte que l'état initial soit différent à chaque fois que nous exécutons le programme. Dans Go, il est courant d'utiliser l'heure actuelle pour amorcer le packagerand
.
Puisque nous voulons que le packagecreature
gère la fonctionnalité aléatoire, ouvrez ce fichier:
nano creature/creature.go
Ajoutez les lignes en surbrillance suivantes au fichiercreature.go
:
creature/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]
}
Dans ce code, nous avons importé le packagetime
et utiliséSeed()
pour amorcer l'heure actuelle. Enregistrez et quittez le fichier.
Maintenant, lorsque nous lancerons le programme, nous obtiendrons un résultat aléatoire:
go run cmd/main.go
Outputjellyfish
octopus
shark
jellyfish
Si vous continuez à exécuter le programme encore et encore, vous continuerez à obtenir des résultats aléatoires. Cependant, ce n'est pas encore une implémentation idéale de notre code, car à chaque fois quecreature.Random()
est appelé, il réinitialisera également le packagerand
en appelant à nouveaurand.Seed(time.Now().UnixNano())
. Le réensemencement augmentera les chances de réensemencer avec la même valeur initiale si l'horloge interne n'a pas changé, ce qui entraînera de possibles répétitions du motif aléatoire ou augmentera le temps de traitement de la CPU en laissant votre programme attendre que l'horloge change.
Pour résoudre ce problème, nous pouvons utiliser une fonctioninit()
. Mettons à jour le fichiercreature.go
:
nano creature/creature.go
Ajoutez les lignes de code suivantes:
creature/creature.go
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]
}
L'ajout de la fonctioninit()
indique au compilateur que lorsque le packagecreature
est importé, il doit exécuter la fonctioninit()
une fois, en fournissant une seule graine pour la génération de nombres aléatoires. Cela garantit que nous n'exécutons pas de code plus que nécessaire. Maintenant, si nous exécutons le programme, nous continuerons à obtenir des résultats aléatoires:
go run cmd/main.go
Outputdolphin
squid
dolphin
octopus
Dans cette section, nous avons vu comment l'utilisation deinit()
peut garantir que les calculs ou initialisations appropriés sont effectués avant l'utilisation du package. Ensuite, nous verrons comment utiliser plusieurs instructionsinit()
dans un package.
Instances multiples deinit()
Contrairement à la fonctionmain()
qui ne peut être déclarée qu'une seule fois, la fonctioninit()
peut être déclarée plusieurs fois dans un package. Cependant, plusieurs instructionsinit()+`s can make it difficult to know which one has priority over the others. In this section, we will show how to maintain control over multiple `+init()
.
Dans la plupart des cas, les fonctionsinit()
s'exécuteront dans l'ordre dans lequel vous les rencontrez. Prenons comme exemple le code suivant:
main.go
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 nous exécutons le programme avec la commande suivante:
go run main.go
Nous recevrons le résultat suivant:
OutputFirst init
Second init
Third init
Fourth init
Notez que chaqueinit()
est exécuté dans l'ordre dans lequel le compilateur le rencontre. Cependant, il n'est pas toujours aussi facile de déterminer l'ordre dans lequel la fonctioninit()
sera appelée.
Examinons une structure de package plus compliquée dans laquelle nous avons plusieurs fichiers contenant chacun leur propre fonctioninit()
déclarée. Pour illustrer cela, nous allons créer un programme qui partage une variable appeléemessage
et l'imprime.
Supprimez les répertoirescreature
etcmd
et leur contenu de la section précédente, et remplacez-les par les répertoires et la structure de fichiers suivants:
├── cmd
│ ├── a.go
│ ├── b.go
│ └── main.go
└── message
└── message.go
Ajoutons maintenant le contenu de chaque fichier. Dansa.go
, ajoutez les lignes suivantes:
cmd/a.go
package main
import (
"fmt"
"github.com/gopherguides/message"
)
func init() {
fmt.Println("a ->", message.Message)
}
Ce fichier contient une seule fonctioninit()
qui imprime la valeur demessage.Message
du packagemessage
.
Ensuite, ajoutez le contenu suivant àb.go
:
cmd/b.go
package main
import (
"fmt"
"github.com/gopherguides/message"
)
func init() {
message.Message = "Hello"
fmt.Println("b ->", message.Message)
}
Dansb.go
, nous avons une seule fonctioninit()
qui définit la valeur demessage.Message
surHello
et l’imprime.
Ensuite, créezmain.go
pour ressembler à ce qui suit:
cmd/main.go
package main
func main() {}
Ce fichier ne fait rien, mais fournit un point d'entrée pour que le programme s'exécute.
Enfin, créez votre fichiermessage.go
comme suit:
message/message.go
package message
var Message string
Notre packagemessage
déclare la variableMessage
exportée.
Pour exécuter le programme, exécutez la commande suivante à partir du répertoirecmd
:
go run *.go
Comme nous avons plusieurs fichiers Go dans le dossiercmd
qui composent le packagemain
, nous devons dire au compilateur que tous les fichiers.go
du dossiercmd
doivent être compilé. L'utilisation de*.go
indique au compilateur de charger tous les fichiers du dossiercmd
qui se terminent par.go
. Si nous émettions la commande dego run main.go
, le programme échouerait à se compiler car il ne verrait pas le code dans les fichiersa.go
etb.go
.
Cela donnera la sortie suivante:
Outputa ->
b -> Hello
Selon la spécification de langue Go pourPackage Initialization, lorsque plusieurs fichiers sont rencontrés dans un package, ils sont traités par ordre alphabétique. Pour cette raison, la première fois que nous avons imprimémessage.Message
à partir dea.go
, la valeur était vide. La valeur n'a pas été initialisée tant que la fonctioninit()
deb.go
n'a pas été exécutée.
Si nous devions changer le nom de fichier dea.go
enc.go
, nous obtiendrions un résultat différent:
Outputb -> Hello
a -> Hello
Maintenant, le compilateur rencontre d'abordb.go
, et en tant que tel, la valeur demessage.Message
est déjà initialisée avecHello
lorsque la fonctioninit()
dansc.go
est rencontrée.
Ce comportement pourrait créer un problème possible dans votre code. Il est courant dans le développement logiciel de changer les noms de fichiers, et en raison de la façon dontinit()
est traité, changer les noms de fichiers peut changer l'ordre dans lequelinit()
est traité. Cela pourrait avoir l’effet indésirable de modifier la sortie de votre programme. Pour assurer un comportement d'initialisation reproductible, les systèmes de génération sont encouragés à présenter plusieurs fichiers appartenant au même paquet dans l'ordre du nom de fichier lexical à un compilateur. Une façon de s'assurer que toutes les fonctionsinit()
sont chargées dans l'ordre est de les déclarer toutes dans un seul fichier. Cela empêchera l'ordre de changer même si les noms de fichiers sont modifiés.
En plus de vous assurer que l'ordre de vos fonctionsinit()
ne change pas, vous devriez également essayer d'éviter de gérer l'état dans votre package en utilisantglobal variables, c'est-à-dire des variables accessibles de n'importe où dans le package. Dans le programme précédent, la variablemessage.Message
était disponible pour l'ensemble du package et conservait l'état du programme. Grâce à cet accès, les instructionsinit()
ont pu modifier la variable et déstabiliser la prévisibilité de votre programme. Pour éviter cela, essayez de travailler avec des variables dans des espaces contrôlés ayant le moins d'accès possible tout en permettant au programme de fonctionner.
Nous avons vu que vous pouvez avoir plusieurs déclarationsinit()
dans un même package. Cependant, cela pourrait créer des effets indésirables et rendre votre programme difficile à lire ou à prédire. Éviter plusieurs instructionsinit()
ou les conserver toutes dans un seul fichier garantira que le comportement de votre programme ne change pas lorsque des fichiers sont déplacés ou que les noms sont modifiés.
Ensuite, nous examinerons commentinit()
est utilisé pour importer avec des effets secondaires.
Utilisation deinit()
pour les effets secondaires
Dans Go, il est parfois souhaitable d’importer un package non pour son contenu, mais pour les effets secondaires qui se produisent lors de l’importation du package. Cela signifie souvent qu'il y a une instructioninit()
dans le code importé qui s'exécute avant tout autre code, permettant au développeur de manipuler l'état dans lequel son programme démarre. Cette technique s'appelleimporting for a side effect.
Un cas d'utilisation courant pour l'importation d'effets secondaires est la fonctionnalitéregister de votre code, qui permet à un package de savoir quelle partie du code votre programme doit utiliser. Dans lesimage
package, par exemple, la fonctionimage.Decode
a besoin de savoir quel format d'image elle tente de décoder (jpg
,png
,gif
, etc. .) avant de pouvoir s'exécuter. Vous pouvez accomplir cela en important d'abord un programme spécifique qui a un effet secondaire de l'instructioninit()
.
Supposons que vous essayez d'utiliserimage.Decode
sur un fichier.png
avec l'extrait de code suivant:
Extrait de décodage
. . .
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
. . .
Un programme avec ce code sera toujours compilé, mais chaque fois que nous essayons de décoder une imagepng
, nous obtiendrons une erreur.
Pour résoudre ce problème, nous devons d'abord enregistrer un format d'image pourimage.Decode
. Heureusement, le packageimage/png
contient l'instructioninit()
suivante:
image/png/reader.go
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
Par conséquent, si nous importonsimage/png
dans notre extrait de code de décodage, la fonctionimage.RegisterFormat()
dansimage/png
s'exécutera avant l'un de nos codes:
Extrait de décodage
. . .
import _ "image/png"
. . .
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
Cela définira l'état et le registre dont nous avons besoin pour la versionpng
deimage.Decode()
. Cet enregistrement se produira comme un effet secondaire de l'importation deimage/png
.
Vous avez peut-être remarqué lesblank identifier (_
) avant"image/png"
. Cela est nécessaire car Go ne vous permet pas d'importer des packages qui ne sont pas utilisés tout au long du programme. En incluant l'identificateur vide, la valeur de l'importation elle-même est ignorée, de sorte que seul l'effet secondaire de l'importation se manifeste. Cela signifie que, même si nous n'appelons jamais le packageimage/png
dans notre code, nous pouvons toujours l'importer pour l'effet secondaire.
Il est important de savoir quand vous devez importer un package pour son effet secondaire. Sans l'enregistrement approprié, il est probable que votre programme compilera, mais ne fonctionnera pas correctement lorsqu'il sera exécuté. Les packages de la bibliothèque standard déclareront la nécessité de ce type d'importation dans leur documentation. Si vous écrivez un package qui nécessite une importation pour effet secondaire, vous devez également vous assurer que l'instructioninit()
que vous utilisez est documentée afin que les utilisateurs qui importent votre package puissent l'utiliser correctement.
Conclusion
Dans ce didacticiel, nous avons appris que la fonctioninit()
se charge avant que le reste du code de votre package ne soit chargé, et qu'elle peut effectuer des tâches spécifiques pour un package comme l'initialisation d'un état souhaité. Nous avons également appris que l'ordre dans lequel le compilateur exécute plusieurs instructionsinit()
dépend de l'ordre dans lequel le compilateur charge les fichiers source. Si vous souhaitez en savoir plus surinit()
, consultez lesGolang documentation officiels ou lisez lesthe discussion in the Go community about the function.
Vous pouvez en savoir plus sur les fonctions avec notre article surHow To Define and Call Functions in Go ou explorerthe entire How To Code in Go series.