Go 의 패키지는 자바의 패키지와 유사하다. 모든 Go 코드는 특정 패키지에 포함되어야 한다. 특수 패키지인 main
을 제외하고는 모두 일반적인 패키지라도 봐도 무방하다.
Hello, Go
다시 한 번, 또 보고 또 본다. 기본적인 코드에 많은 내용이 들어있다.
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
위의 코드는 main
패키지에 소속되어 있음을 볼 수 있다. main
은 특수 패키지이며 프로그램의 메인 코드인 main()
함수가 정의되어 있으며 실행가능한 패키지임을 의미한다. 이 패키지는 다른 코드에 의해 포함되는 용도로 존재하는 것이 아니라 프로그램을 실행하기 위해 존재한다. 이렇듯, 우리가 가장 처음 작성했던 코드마저도 패키지에 소속되어 있는 것을 볼 수 있는데, Go 의 모든 코드는 어떠한 패키지 내부에 포함되어 있다.
내장 패키지는 어디에 있을까?
Hello, Go 의 코드를 보면 import
키워드를 사용하여 fmt
패키지를 포함하고 있는 것을 볼 수 있다. 이 패키지는 내장 패키지이며 go 의 바이너리 파일이 있는 디렉토리의 상위 폴더를 보면 pkg, src 폴더가 있을 텐데, src 폴더에 내장 패키지의 go 파일들이 있는 것을 볼 수 있다.
├── $GOROOT/
│ ├── pkg/
│ ├── src/
│ │ ├── fmt/
│ │ ├── crypto/
│ │ ├── os/
│ │ ├── strings/
│ ├── bin/
│ │ ├── go.exe
src 폴더에 내장 패키지가 있는 것을 알았으면, pkg 폴더는 무엇이란 말인가? 여기에는 내장 패키지들이 컴파일 된 것들이 들어있다. 따라서 우리가 주목해야 하는 것은 src 폴더이다.
패키지 만들기
import
키워드를 사용하여 패키지를 포함할 때 go 가 찾는 폴더에는 GOROOT/src 가 포함된다. 여기서 GOROOT 는 환경 변수의 값으로 대체된다. Go 의 환경변수에는 GOROOT 와 GOPATH 가 있는데, 우리가 go get
을 통해 임의로 설치한 패키지는 일반적으로 GOPATH 로 가며 내장 패키지의 경우 GOROOT 에 있지만, GOROOT 에 임의로 패키지를 추가해도 제대로 동작한다. GOROOT/src 폴더에 hello
패키지를 만들어서 실험을 해보자. 폴더 구조는 아래와 같다.
├── $GOROOT/
│ ├── src
│ │ ├── example.com/
│ │ │ ├── hello
│ │ │ │ ├── hello.go
hello.go
의 코드는 다음과 같다. 패키지의 경로가 example.com/hello
인 것을 주목하자. 또한 함수의 이름이 대문자인 것도 기억하자. 이것은 Go 언어가 가진 아주 중요한 특징이다.
package hello
import "fmt"
func SayHello() {
fmt.Println("Hello, Go!")
}
그 다음, 이제 어느 곳에서든 main
패키지를 작성하고 hello
패키지를 불러온 뒤, 함수를 실행하자.
package main
import "example.com/hello"
func main() {
hello.SayHello()
}
아주 놀랍게도 Go 에서는 함수, 변수, 상수, 구조체 등의 이름이 대문자가 아니라면 외부 패키지로 노출하지 않는다. 즉, 소문자의 경우 캡슐화되어 패키지 내부에서만 사용할 수 있고 외부에서는 접근할 수 없다. 이것이 우리가 패키지를 작성할때 함수의 이름을 대문자로 지은 이유다.
같은 패키지 내부에 있다면 파일이 여러 개로 분리되어있는 것은 아무런 상관없다.
또 한 가지 주목해야 하는 점은, 패키지 경로와 패키지 이름이다. 패키지 경로는 example.com/hello
이며, 패키지 이름은 hello
이다. 내장 패키지 fmt
의 경우에는 패키지 경로와 이름이 같았지만, 우리가 만든 패키지는 일부러 그렇게 하지 않았다.
go get
go get
을 통해서 다른 패키지를 다운받아 사용할 수 있다. 얻어온 패키지는 일반적으로 GOPATH 로 다운로드 된다. GOPATH 는 작업공간이라고도 하며 사용자마다 별도로 관리된다. 폴더 구조자체는 GOROOT 와 같으나 관리 주체가 다르기때문에 이는 알아둘 필요가 있다.
패키지를 만드는 다른 방법은 없을까?
우리가 패키지를 만들 때마다 GOROOT, GOPATH 의 src 폴더에 패키지를 만들고 테스트하는 것은 상당히 비효율적이다. 그래서 우리는 Go 패키지를 다른 방식으로 만들어 볼 것이다. 아래에 소개하는 문서를 봐도 좋고, 현재 포스트의 문서를 따라와도 된다.
https://golang.org/doc/tutorial/create-module
Tutorial: Create a Go module - The Go Programming Language
Tutorial: Create a Go module This is the first part of a tutorial that introduces a few fundamental features of the Go language. If you're just getting started with Go, be sure to take a look at the getting started tutorial, which introduces the go command
golang.org
우선 폴더구조는 아래와 같다. main.go
코드는 main
패키지이며 위에서 hello
패키지를 불러와서 하는 것과 같다. hello.go
의 코드는 위에서 작성한 hello
패키지의 코드와 동일하니 생략하자.
├── /
│ ├── main.go
│ ├── example.com/
│ │ ├── hello/
│ │ │ ├── hello.go
go mod
사용자 정의 go 모듈을 만들 때 사용한다. example.com
폴더로 진입하여 go mod init example.com
명령어를 통해 모듈을 정의하자. 그렇게되면 go.mod
파일이 다음과 같이 생긴다. go 모듈을 정의할 때는 모듈 루트에 해야하며 example.com
은 hello
패키지를 포함하는 모듈의 루트다.
module example.com
go 1.13
이제 다시 밖으로 나와서 go mod init main
으로 main
패키지에서 사용할 종속성을 만들어야 한다. 패키지 경로에 example.com
을 포함한다면, 동일 디렉토리에 있는 example.com
폴더에서 찾는다.
module main
go 1.13
replace example.com => ./example.com
go run main.go
를 통해 실행시켜보면 go.mod
파일이 변경되어 있음을 알 수 있다. go.mod
파일은 go 패키지의 종속성을 관리하므로 버전과 관련된 정보가 나와있음을 알 수 있다.
module main
go 1.13
replace example.com => ./example.com
require example.com v0.0.0-00010101000000-000000000000 // indirect
├── /
│ ├── main.go
│ ├── go.mod
│ ├── example.com/
│ │ ├── hello/
│ │ │ ├── hello.go
│ │ ├── go.mod
공식 문서에서 패키지 찾기
Go 의 내장 패키지는 공식 홈페이지 패키지 문서쪽에서 찾을 수 있다. 패키지 목록은 외우는 것이 아니다. 어떠한 패키지가 있고 이 패키지가 무엇을 하는 것인지, 어떨 때 써야할지만 살펴보면 된다.
Packages - The Go Programming Language
Packages
go doc
go doc
명령어는 go 패키지의 사용법을 살펴볼때 사용한다. 문서화와 관련된 도구가 내장되어 있는 것은 아주 좋다고 볼 수 있는데, 예를 들어 fmt
패키지의 Println()
함수에 대한 사용법이 궁금하다면 go doc fmt Println
을 사용하면 다음과 같이 출력된다.
$ go doc fmt Println
package fmt // import "fmt"
func Println(a ...interface{}) (n int, err error)
Println formats using the default formats for its operands and writes to
standard output. Spaces are always added between operands and a newline is
appended. It returns the number of bytes written and any write error
encountered.