원문 : https://medium.com/golangspec/init-functions-in-go-eac191b3860a
전체 내용은 원문에서 확인하실 수 있으며, 또 내용 중 일부 오역이 포함되어 있을 수 있으니, 가능하면 원문을 참조하시는 걸 추천드립니다.
main은 어디에나 있습니다. 모든 Go 프로그램은 패키지에서 main이라 불리는 함수에서 시작합니다. 이 main 함수가 리턴할 때, 프로그램은 종료됩니다. 또한 init 함수도 특별한 역할을 합니다. 이 글은 init 함수의 속성에 대해 설명하고 어떻게 사용하는지 소개합니다.
init 함수는 패키지 블록에서 정의하고 아래의 목적으로 사용합니다.
- 초기화 표현으로 변수를 초기화 할 수 없을 때
- 검증하거나, 프로그램 상태를 고쳐야 할 때
- 등록하기
- 한번만 계산 되어야 할 때
- 기타 등등
아래에서 몇가지 다른 점을 얘기하지만, 일반적인 함수에서 넣을 수 있는 어떤 것도 사용할 수 있습니다.
패키지 초기화
import한 패키지를 사용하기 위해서는 먼저 초기화가 필요합니다. Golang의 실행 체계에서 수행되고 다음과 같이 구성됩니다.
1. 임포트 된 패키지의 초기화 ( 재귀적 정의 )
2. 연산과 패키지 블록에서 선언한 변수의 초기값 할당.
3. 패키지에서 init 함수 실행.
패키지 초기화는 패키지가 여러번 import 되더라도 한번만 수행 됩니다.
순서.
Go에서 패키지에는 많은 파일이 있을 수 있습니다. 만약 많은 패키지의 파일 어지럽게 섞여 있다면, 무엇이 변수의 초기화와 init 함수의 실행 순서를 정할까요? 첫번째, 초기화 의존성 체계로 시작합니다. (더 자세한 설명은 "Go에서 의존성 초기화"에 있습니다. ) 이게 완료되면, a.go 또는 z.go 파일의 변수를 먼저 초기화해야 하는지 결정해야 합니다. 이 것은 컴파일러에 표시되는 순서에 따라 다릅니다. 빌드에서 z.go가 먼저 통과했다면 a.go보다 먼저 수행됩니다. init 함수의 실행도 동일하게 적용됩니다. 언어 사양에서는 항상 동일한 순서로 사용하고 파일이름을 어휘 순으로 전달하는 것을 추천합니다.
초기화를 동일하게 동작하게 하기위해, 빌드 시스템은 동일한 패키지에 속하는 여러 파일을 어휘 순으로 컴파일러에 전달하는 것을 추천합니다.
하지만, 특정 순서에 의지하는 것은 프로그램의 이식 성을 떨어뜨립니다. 여러 파일들이 동작하는 예를들어 보겠습니다.
sandbox.go
package main
import "fmt"
var _ int64 = s()
func init() {
fmt.Println("init in sandbox.go")
}
func s() int64 {
fmt.Println("calling s() in sandbox.go")
return 1
}
func main() {
fmt.Println("main")
}
a.go
package main
import "fmt"
var _ int64 = a()
func init() {
fmt.Println("init in a.go")
}
func a() int64 {
fmt.Println("calling a() in a.go")
return 2
}
z.go
package main
import "fmt"
var _ int64 = z()
func init() {
fmt.Println("init in z.go")
}
func z() int64 {
fmt.Println("calling z() in z.go")
return 3
}
program outputs
calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main
속성
init 함수는 인자를 받지 않고, 어떤 값도 리턴하지 않습니다. main과 달리, init 선언되지 않으므로 참조할 수 없습니다.
package main
import "fmt"
func init() {
fmt.Println("init")
}
func main() {
init()
}
위 코드는 컴파일 할 때, "undefined: init" 에러가 발생합니다.
공식적으로 init 지시자는 바인딩 되지 않습니다. 같은 방식으로 '_' 문자 표현되는 빈 지시자도 바인딩 되지 않습니다.
많은 init 함수가 동일한 패키지서 선언될 수 있습니다.
sandbox.go
package main
import "fmt"
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}
utils.go
package main
import "fmt"
func init() {
fmt.Println("init 3")
}
출력 :
init 1
init 2
init 3
main
init 함수는 표준 라이브러에서 빈번히 사용됩니다. 예 ) math, bzip2, image 패키지.
init 함수의 가장 일반적인 사용은 초기화 표현식에서 계산할 수 없는 값의 할당입니다.
var precomputed = [20]float64{}
func init() {
var current float64 = 1
precomputed[0] = current
for i := 1; i < len(precomputed); i++ {
precomputed[i] = precomputed[i-1] * 1.2
}
}
Go 표현식으로 반복을 사용할 수 없기 때문에 init 함수안에서 문제를 해결합니다.
패키지 import의 부작용을 피하기.
Go는 사용하지 않는 import에 아주 엄격합니다. 어떨때는 프로그래머가 init 함수만 실행하기 위해 import를 하고 싶을 수 있습니다. 예를 들어 부트 스트랩 작업 같은 .. 빈 지시자는 그 문제를 해결합니다.
import _ "image/png"
이런 방식은 image 패키지의 주석에도 언급되어 있습니다.
java에서의 class construtor나 ruby에서 initialize 함수가 go의 init함수와 비슷한 역할을 하지만, go에서는 클래스가 없고, init 함수를 여러번 쓸 수 있다는 점, 실행 순서가 좀 다른 점이 있어 저처럼 다른 언어에 익숙하신 분이라면, 주의할 필요가 있을 것 같습니다. Go에서 init 함수가 동작하는 방식을 잘 기억하고 있어야 애플리케이션을 만들때, 혼랑을 피할 수 있을 것 같습니다.