Wszystko, co musisz wiedzieć o pakietach w Go

Inicjalizacja pakietu

Gdy uruchamiamy program Go, kompilator Go przestrzega pewnej kolejności wykonywania pakietów, plików w pakiecie i deklaracji zmiennych w tych plikach.

Zakres pakietu

Zakres to region w bloku kodu, w którym zdefiniowana zmienna jest dostępna. Zakres pakietu jest regionem w pakiecie, gdzie zadeklarowana zmienna jest dostępna z pakietu (przez wszystkie pliki w pakiecie). Ten region jest najwyżej położonym blokiem dowolnego pliku w pakiecie.

Przyjrzyjrzyj się poleceniu go run. Tym razem, zamiast wykonywać jeden plik, mamy wzorzec glob do włączenia wszystkich plików wewnątrz pakietu app do wykonania.

Go jest wystarczająco inteligentny, aby dowiedzieć się punkt wejścia aplikacji, która jest entry.go, ponieważ ma main funkcji. Możemy również użyć komendy jak poniżej (kolejność plików nie ma znaczenia).

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

💡 Można również użyć komendy go run app, która robi to samo i jest to lepsze podejście do wykonania pakietu. Polecenie go install lub go build wymaga nazwy pakietu, która zawiera wszystkie pliki wewnątrz pakietu, więc nie musimy ich określać jak powyżej.

Wracając do naszego głównego problemu, możemy użyć zmiennej version zadeklarowanej w pliku version.go z dowolnego miejsca w pakiecie, nawet jeśli nie jest ona wyeksportowana (jak Version), ponieważ jest zadeklarowana w zakresie pakietu.

Jeśli zmienna version byłaby zadeklarowana wewnątrz funkcji, nie byłaby w zakresie pakietu i powyższy program nie skompilowałby się.

Nie wolno ponownie zadeklarować zmiennej globalnej o tej samej nazwie w tym samym pakiecie. Stąd, gdy version zmienna jest zadeklarowana, nie może być ponownie zadeklarowana w zakresie pakietu. Ale możesz ją ponownie zadeklarować gdzie indziej.

Inicjalizacja zmiennych

Gdy zmienna a zależy od innej zmiennej b, b powinna być wcześniej zdefiniowana, w przeciwnym razie program nie skompiluje się. Go przestrzega tej zasady wewnątrz funkcji.

Ale kiedy te zmienne są zdefiniowane w zakresie pakietu, są one deklarowane w cyklach inicjalizacji. Spójrzmy na prosty przykład poniżej.

W powyższym przykładzie, najpierw c jest zadeklarowany, ponieważ jego wartość jest już zadeklarowana. W późniejszym cyklu inicjalizacji deklarowany jest b, ponieważ zależy on od c, a wartość c jest już zadeklarowana. W ostatnim cyklu inicjalizacji, a jest deklarowany i przypisywany do wartości b. Go może obsługiwać złożone cykle inicjalizacji, takie jak poniżej.

W powyższym przykładzie najpierw c zostanie zadeklarowany, a następnie b, ponieważ jego wartość zależy od c, a na końcu a, ponieważ jego wartość zależy od b. Należy unikać wszelkich pętli inicjalizacji, takich jak poniżej, gdzie inicjalizacja wpada w pętlę rekurencyjną.

Innym przykładem zakresu pakietu byłoby posiadanie funkcji f w oddzielnym pliku, która odwołuje się do zmiennej c z pliku głównego.

Init function

Podobnie jak funkcja main, init funkcja jest wywoływana przez Go, gdy pakiet jest inicjalizowany. Nie przyjmuje ona żadnych argumentów i nie zwraca żadnej wartości.

Funkcja init jest niejawnie zadeklarowana przez Go, stąd nie można się do niej odwoływać z dowolnego miejsca (lub wywoływać jej jak init()). Możesz mieć wiele funkcji init w pliku lub pakiecie. Kolejność wykonywania funkcji init w pliku będzie zgodna z kolejnością ich pojawiania się.

Możesz mieć funkcję init w dowolnym miejscu w pakiecie. Te init funkcje są wywoływane w kolejności leksykalnej nazwy pliku (kolejność alfabetyczna).

Po wykonaniu wszystkichinit funkcji wywoływana jest main funkcja. Stąd głównym zadaniem funkcji init jest inicjalizacja zmiennych globalnych, które nie mogą być inicjalizowane w kontekście globalnym. Na przykład, inicjalizacja tablicy.

Ponieważ, składnia for nie obowiązuje w zakresie pakietu, możemy zainicjalizować tablicę integers o rozmiarze 10 używając pętli for wewnątrz funkcji init.

Alias pakietu

Gdy importujesz pakiet, Go tworzy zmienną używając deklaracji pakietu. Jeśli importujesz wiele pakietów o tej samej nazwie, doprowadzi to do konfliktu.

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

W związku z tym używamy aliasu pakietu. Pomiędzy słowem kluczowym import a nazwą pakietu podajemy nazwę zmiennej, która staje się nową zmienną odwołującą się do pakietu.

W powyższym przykładzie, pakiet greet/greet jest teraz odwoływany przez zmienną child. Jeśli zauważyłeś, aliasowaliśmy pakiet greet za pomocą podkreślenia.

Underscore jest specjalnym znakiem w Go, który działa jak null kontener. Ponieważ importujemy pakiet greet, ale go nie używamy, kompilator Go będzie narzekał na to. Aby tego uniknąć, przechowujemy referencję do tego pakietu w _ i kompilator Go po prostu ją zignoruje.

Aliasowanie pakietu podkreślnikiem, który wydaje się nic nie robić, jest całkiem przydatne czasami, gdy chcesz zainicjować pakiet, ale go nie używać.

// 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()")
}

Główną rzeczą do zapamiętania jest to, że importowany pakiet jest inicjalizowany tylko raz na pakiet. Stąd, jeśli masz wiele deklaracji importu w pakiecie, importowany pakiet będzie inicjalizowany tylko raz w czasie wykonywania pakietu głównego.