Go ha regole rigorose per l'inizializzazione di pacchetti, variabili e funzioni all'avvio del programma. Il meccanismo principale è l'esecuzione delle funzioni init e l'inizializzazione delle variabili globali. Comprendere correttamente questi processi è importante per evitare errori e effetti imprevisti.
Storia della domanda:
In Go, fin dall'inizio, è stata introdotta una netta separazione delle fasi di avvio: dichiarazione, inizializzazione e successiva esecuzione del codice. In linguaggi come C/C++, si usano frequentemente i costruttori per le variabili globali, mentre in Go l'ordine di inizializzazione è deterministico, ma ci sono delle sfumature.
Problema:
È facile incorrere in trappole quando l'inizializzazione delle variabili globali o la chiamata di init porta a situazioni di interdipendenza o cicliche tra pacchetti. Questo è difficile da tracciare e i programmi possono comportarsi in modo diverso rispetto a quanto si aspetta lo sviluppatore, specialmente in presenza di dipendenze nascoste o isolamento dello stato all'avvio.
Soluzione:
I pacchetti in Go vengono inizializzati in un ordine determinato dalle loro dipendenze: prima le dipendenze, poi il pacchetto stesso. Prima si inizializzano le variabili di livello pacchetto (nell'ordine in cui appaiono nel file sorgente), poi viene chiamata qualsiasi funzione init(), se presente. È possibile dichiarare più init() in un singolo file. L'ordine di inizializzazione tra i file di un pacchetto non è definito (e questo può portare a errori).
Esempio di codice:
// a.go package main import "fmt" func init() { fmt.Println("init da a.go") } // b.go package main import "fmt" func init() { fmt.Println("init da b.go") }
Il risultato dell'esecuzione di queste funzioni init non è prevedibile tra i file di una stessa directory, ma avviene sempre prima della funzione main().
Caratteristiche chiave:
È possibile fare affidamento sull'ordine di esecuzione delle funzioni init in diversi file di uno stesso pacchetto?
No! Go non garantisce l'ordine tra le funzioni init di diversi file in uno stesso pacchetto. Le speranze di un certo ordine possono tradursi in errori difficili da individuare e nel crollo della logica aziendale.
Possono le variabili globali non essere inizializzate al momento dell'esecuzione della funzione init?
No — tutte le variabili globali del pacchetto vengono eseguite rigorosamente nell'ordine di dichiarazione fino a tutte le funzioni init di quel pacchetto. Le eccezioni sono solo le inizializzazioni incrociate tra pacchetti (vedi sotto).
Come evitare dipendenze cicliche init tra pacchetti?
Go non consente importazioni cicliche a livello di pacchetti (questa è un'errore di compilazione), ma si può incorrere in trappole di inizializzazione indiretta: A dipende da B, B da C, e C (attraverso una variabile globale o init) chiama codice da A. In questi casi può sorgere un ordine di chiamata non ovvio per init/costruttori globali.
Nel team, le logiche di inizializzazione dei servizi sono eseguite in diverse funzioni init di file diversi. Una init dipende dal risultato dell'altra, il che porta a un comportamento casuale tra build e avvio su diversi server.
Vantaggi:
Svantaggi:
Tutto lo stato e l'inizializzazione sono eseguiti con chiamate esplicite in main(). Le funzioni init vengono utilizzate esclusivamente per il tracciamento dell'avvio e piccole verifiche.
Vantaggi:
Svantaggi: