패키지, 임포트에 대한 심화 학습

규칙을 따르지 않으면 손발이 고생한다.

Go도 당연히 다른 언어처럼 작성한 코드의 재사용이 가능하다. 쉬이 예상 가능하듯, 패키지를 만들고 이를 임포트해 재사용할 수 있다.

그런데 Go는 C++이나 Java와는 달리 접근제한자(access modifier)가 없다. 접근제한자 대신 매우 단순한 한 가지 규칙에 따라 외부 접근 가능 여부를 판단한다.

외부 패키지에서 접근이 가능하게 하려면—export 하려면— 첫 번째 문자를 대문자로 하라.

이 규칙은 함수나 변수, 상수뿐만 아니라 구조체와 관련된 필드, 메서드 등에도 그대로 적용된다.

대문자에 대해서 좀 더 자세히 언급하면, Unicode “Lu” class에 포함되는 문자를 의미한다. 즉, 𝓐bc 이런 이름도 외부에서 접근할 수 있지만, 안타깝게도 한글은 포함되지 않는다.

Python도 접근제한자 없이 underscore(_)나 double underscore(__) 등을 사용해 접근 범위를 지정하지만 ‘우린 모두 19금을 볼 수 있지만 굳이 볼 필요가 없는 것은 안 볼 수도 있는 성숙한 어른이잖아요’ 라며 접근 시도를 자율에 맡기는 반면, Go는 아직 충분히 크지 않은 경우—대문자가 아닌 경우— 에는 접근을 강제로 불허한다.

패키지 만들기

디렉토리 구조는 다음과 같다.

$GOPATH -+- myapp  --- main.go
         |
         +- mypack -+- packa1 -+- packa1.go
                    |          |
                    |          +- packb.go
                    |
                    +- packa2 --- packa2.go

패키지 경로와 이름

관례상(by convention) 패키지의 경로와 이름을 같게 할 뿐 둘은 엄연히 다르다.

위 디렉토리 구조에서 packa2, packa2.go, packb.go는 없고, packa1.go를 다음과 같이 작성한 상황을 가정해 보자.

// packa1.go
package packa

import "fmt"

func PackA1() {
	fmt.Println("This is PackA1()")
}

이 패키지를 사용하기 위한 main.go는 다음과 같다.

// main.go
package main

import "mypack/packa1"	// PATH

func main() {
	packa.PackA1()		// NAME
}

한 경로에 두 가지 이상의 패키지를 두면…

그럼 한 디렉토리에 있는 소스 파일들이 서로 다른 패키지 이름을 갖는다면 어떻게 될까?

위 예에서 packb.go 파일을 만들고 다음과 같이 작성한 것을 가정한 것이다.

// packb.go
package packb

import "fmt"

func PackB() {
	fmt.Println("This is PackB()")
}

이 경우 빌드 시 다음과 같은 오류가 발생한다.

found packages packa (packa1.go) and packb (packb.go) in ~~~/mypack/packa1

한 디렉토리에 다른 이름을 갖는 패키지들이 있다고 Go가 싫어한다.

한 패키지를 둘 이상의 경로에 두면…

반대로 같은 패키지 이름을 갖는 소스 파일들을 여러 경로에 분산해 두면 어떻게 될까?

packb.go는 삭제하고, packa2 디렉토리를 만들어 그 아래에 packa2.go를 다음과 같이 작성한 것을 가정한 것이다.

// packa2.go
package packa

import "fmt"

func PackA2() {
	fmt.Println("This is PackA2()")
}

PackA2() 를 호출하기 위해 main.go도 다음과 같이 수정한다.

// main.go
package main

import "mypack/packa1"
import "mypack/packa2"

func main() {
	packa.PackA1()
	packa.PackA2()
}

이 경우에는 빌드 시 다음과 같은 오류가 발생한다.

./main.go:5:8: packa redeclared as imported package name
	previous declaration at ./main.go:4:8
./main.go:8:2: undefined: "mypack/packa2".PackA1

mypack/packa2 경로에 있는 패키지를 임포트하면서 mypack/packa1에서 이미 임포트한 packa가 재정의 되었다는 오류와 이로 인해—mypack/packa2에서 임포트한 패키지가 mypack/packa1에서 임포트한 패키지를 덮어 씀으로 인해— 패키지 packa에서는 PackA1 함수를 찾을 수 없다는 오류이다.

이 문제는 임포트하는 패키지에 별칭(alias)을 달아 해결 가능하다.

// main.go
package main

import a1 "mypack/packa1"
import a2 "mypack/packa2"

func main() {
	a1.PackA1()
	a2.PackA2()
}

결론

관례라는 용어가 특수활동비 문제로 인해 답습과 비슷한 뜻으로 이해되기는 하지만 최소환 Go의 패키지 경로와 이름에 관련해서는 다 이유가 있어 생긴 것이다. 어떻게 되는지 봤으니 괜히 이상한 시도 하지 말자. 복잡해진다.