Minden, amit a csomagokról tudni kell Go-ban

Pakett inicializálás

Mikor egy Go programot futtatunk, a Go fordító egy bizonyos végrehajtási sorrendet követ a csomagok, a csomagban lévő fájlok és az ezekben lévő változó deklarációk esetében.

Pakett hatókör

A hatókör egy olyan régió a kódblokkban, ahol egy definiált változó elérhető. A csomag hatókör egy olyan terület egy csomagon belül, ahol egy deklarált változó elérhető a csomagon belül (a csomag összes fájljában). Ez a régió a csomag bármely fájljának legfelső blokkja.

Nézd meg a go run parancsot. Ezúttal egy fájl végrehajtása helyett egy glob mintát használunk, hogy a app csomagon belüli összes fájlt bevonjuk a végrehajtáshoz.

Go elég okos ahhoz, hogy kitalálja az alkalmazás belépési pontját, ami a entry.go, mivel rendelkezik main funkcióval. Használhatunk egy olyan parancsot is, mint az alábbi (a fájlnevek sorrendje nem számít).

go run src/app/version.go src/app/entry.go

💡 Használhatjuk a go run app parancsot is, amely ugyanezt teszi, és ez egy jobb megközelítés a csomag végrehajtásához. A go install vagy go build parancs egy csomagnevet igényel, amely tartalmazza a csomagon belüli összes fájlt, így nem kell megadnunk őket, mint fentebb.

A fő problémánkhoz visszatérve, a version.go fájlban deklarált version változót bárhol használhatjuk a csomagban, még akkor is, ha nincs exportálva (mint Version), mert a csomag hatókörében van deklarálva.

Ha a version változót egy függvényen belül deklaráltuk volna, akkor nem lett volna a csomag hatókörében, és a fenti program nem tudott volna lefordítani.

Egy globális változót nem szabad újra deklarálni ugyanazzal a névvel ugyanabban a csomagban. Ezért a version egyszer deklarált változót nem lehet újra deklarálni a csomag hatókörében. Máshol azonban szabadon újra deklarálható.

Változók inicializálása

Ha egy változó a egy másik változótól b függ, b előzetesen meg kell határozni, különben a program nem fordítható le. A Go ezt a szabályt függvényeken belül követi.

De ha ezeket a változókat a csomag hatókörében definiáljuk, akkor az inicializációs ciklusokban deklaráljuk őket. Nézzük meg az alábbi egyszerű példát.

A fenti példában először c van deklarálva, mert az értéke már deklarálva van. A későbbi inicializálási ciklusban a b kerül deklarálásra, mert függ a c-től, és a c értéke már deklarálva van. Az utolsó inicializálási ciklusban a kerül deklarálásra és hozzárendelésre b értékéhez. A Go képes kezelni az alábbi összetett inicializálási ciklusokat:

A fenti példában először c lesz deklarálva, majd b, mivel annak értéke c-től függ, és végül a, mivel annak értéke b-től függ. Kerülni kell minden olyan inicializálási hurkot, mint az alábbiakban, ahol az inicializálás rekurzív ciklusba kerül.

Egy másik példa a csomag hatókörére az lenne, ha a f függvény egy külön fájlban lenne, amely hivatkozik a c változóra a főfájlból.

Init funkció

Mint main funkció, init függvényt a Go hívja meg egy csomag inicializálásakor. Nem fogad el semmilyen argumentumot, és nem ad vissza semmilyen értéket.

A init függvényt a Go implicit módon deklarálja, ezért nem hivatkozhatunk rá sehonnan (vagy hívhatjuk meg, mint a init() függvényt). Egy fájlban vagy csomagban több init függvény is lehet. A init függvények végrehajtási sorrendje egy fájlban a megjelenésük sorrendje szerint lesz.

A csomagban bárhol lehet init függvényed. Ezek a init függvények lexikális fájlnévsorrendben (ábécésorrendben) kerülnek meghívásra.

Az összes init függvény végrehajtása után a main függvény kerül meghívásra. A init függvény fő feladata tehát a globális változók inicializálása, amelyek nem inicializálhatók a globális kontextusban. Például egy tömb inicializálása.

Mivel a for szintaxis nem érvényes a csomag hatókörében, a init függvényen belüli for ciklus segítségével inicializálhatjuk a integers méretű 10 tömböt.

Package alias

Amikor importálunk egy csomagot, Go hozzunk létre egy változót a csomag csomag deklarációját használva. Ha több azonos nevű csomagot importálunk, ez konfliktushoz vezet.

// greet/parent.go
package greetvar Message = "Hey there. I am parent."// greet/greet/child.go
package greetvar Message = "Hey there. I am child."

Ezért használjuk a package alias-t. A import kulcsszó és a csomag neve között megadunk egy változó nevét, amely a csomagra való hivatkozás új változója lesz.

A fenti példában a csomagra most a child változóval hivatkozunk. Ha észrevetted, a greet csomagot egy aláhúzással aliasosítottuk.

Az aláhúzás egy speciális karakter a Go-ban, amely null tárolóként működik. Mivel importáljuk a greet csomagot, de nem használjuk, a Go fordító panaszkodni fog emiatt. Hogy ezt elkerüljük, a csomag hivatkozását a _-be tároljuk, és a Go fordító egyszerűen figyelmen kívül hagyja.

A csomagot egy aláhúzással, ami látszólag semmit sem csinál, néha nagyon hasznos, amikor inicializálni akarunk egy csomagot, de nem használjuk.

// greet/parent.go
package greetimport "fmt"var Message = "Hey there. I am parent."func init() {
fmt.Println("greet/parent.go ==> init()")
}// greet/greet/child.go
package greetimport "fmt"var Message = "Hey there. I am child."func init() {
fmt.Println("greet/greet/child.go ==> init()")
}

A legfontosabb, hogy egy importált csomagot csomagonként csak egyszer inicializálunk. Ezért ha több import utasítás van egy csomagban, akkor egy importált csomag csak egyszer lesz inicializálva a főcsomag futtatásának élettartama alatt.