Wann wird init() in Go ausgeführt? Analyse anhand eines realen Beispiels
Stellen Sie sich vor: Sie schreiben einen Mikroservice in Go. In einem der Pakete haben Sie init() deklariert, um die Konfiguration zu laden. Sie starten die Anwendung – und plötzlich stürzt sie mit nil pointer dereference ab. Sie geraten in Panik: "Warum? Die Konfiguration hätte doch zuerst geladen werden müssen!"
Kommt Ihnen das bekannt vor? Wenn ja, sind Sie nicht allein. Die Frage "When does the init() function run?" ist eine der beliebtesten auf Stack Overflow (tausende Aufrufe). Lassen Sie uns ein für alle Mal klären, wann und in welcher Reihenfolge init() in Go ausgeführt wird, damit Sie nicht mehr in diese Falle tappen.
Was ist init() und wozu dient es?
In Go ist init() eine spezielle Funktion ohne Parameter und Rückgabewert. Sie wird nicht explizit aufgerufen. Die Go-Laufzeitumgebung führt sie automatisch vor der Ausführung der Funktion main() aus.
Wozu wird das benötigt?
- Initialisierung globaler Variablen, die eine komplexe Logik erfordern (z. B. Lesen aus einer Datei).
- Registrierung von Datenbanktreibern oder HTTP-Handlern.
- Überprüfung der Umgebung (Umgebungsvariablen, Verfügbarkeit von Ressourcen).
Aber die Hauptregel lautet: Sie sollten sich niemals auf die Ausführungsreihenfolge von init() zwischen verschiedenen Dateien desselben Pakets verlassen. Und hier ist der Grund dafür.
Wann genau wird init() ausgeführt? Die genaue Reihenfolge
Go führt init() nach strengen Regeln aus. Lassen Sie uns diese im Detail betrachten.
Regel Nr. 1: Ein Paket – eine Reihenfolge
Innerhalb eines Pakets wird init() in der Reihenfolge der Dateideklarationen ausgeführt (alphabetisch nach Dateinamen). Dies ist jedoch ein Implementierungsdetail – darauf sollte man sich besser nicht verlassen.
Regel Nr. 2: Zuerst Variableninitialisierung, dann init()
Vor dem Aufruf von init() initialisiert Go alle globalen Variablen im Paket. Wenn eine Variable von einer anderen Variable im selben Paket abhängt, ist die Reihenfolge ihrer Deklaration entscheidend.
package config
var AppName string = "MyApp"var Version string = getVersion() // wird vor init() aufgerufen
func init() { // Hier sind AppName und Version bereits verfügbar println("Init gestartet für", AppName)}
func getVersion() string { return "1.0.0"}Regel Nr. 3: Importierte Pakete werden zuerst initialisiert
Dies ist die wichtigste Regel. Wenn Paket A Paket B importiert, werden zuerst alle init() in Paket B ausgeführt und erst dann die in A. Go erstellt einen Abhängigkeitsgraphen und führt die Initialisierung "von unten nach oben" durch.
// Paket databasepackage database
func init() { println("1. database init")}
// Paket main (importiert database)package main
import "myapp/database"
func init() { println("2. main init")}
func main() { println("3. main function")}
// Ausgabe:// 1. database init// 2. main init// 3. main functionRegel Nr. 4: Mehrere init() in einer Datei – möglich, aber nicht nötig
In einer Datei können mehrere init() vorhanden sein. Sie werden in der Reihenfolge ihrer Deklaration ausgeführt. Dies erschwert jedoch die Lesbarkeit des Codes. Es ist besser, die Logik in einer Funktion zu bündeln.
Lebendiges Beispiel: Was passiert, wenn man die Reihenfolge falsch einschätzt?
Lassen Sie uns einen typischen Anfängerfehler simulieren. Angenommen, wir haben ein Paket config, das Einstellungen aus einer Datei liest, und ein Paket app, das diese Einstellungen verwendet.
// config.gopackage config
var Settings map[string]string
func init() { // Simuliert das Lesen einer Datei Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Konfiguration geladen")}
// app.gopackage app
import "myapp/config"
var ServerPort string
func init() { // FEHLER: wenn config.Settings noch nicht initialisiert ist