본문 바로가기
프로그래밍/Go

Go package main과 패키지 구조 이해하기

by JLearn 2026. 7. 10.
반응형

Go 소스 파일은 package 선언으로 시작합니다. 이 선언은 해당 파일이 어떤 패키지에 속하는지를 나타냅니다.

특히 package main은 실행 가능한 프로그램을 만들 때 사용하는 특별한 패키지 선언입니다. 이 글에서는 package main의 의미와 디렉터리, 패키지 이름, 여러 Go 파일 사이의 관계를 실제 예제로 정리합니다.


핵심 개념

Go에서 패키지를 이해할 때는 다음 세 가지를 구분해야 합니다.

구분 의미
디렉터리 Go 소스 파일이 저장된 실제 폴더
패키지 이름 소스 파일의 package 이름에 선언된 이름
패키지 경로 다른 패키지에서 import할 때 사용하는 경로

예를 들어 다음 구조가 있다고 가정합니다.

task-api/
├── go.mod
├── main.go
└── message.go

두 Go 파일이 모두 다음과 같이 시작한다면 하나의 패키지로 함께 컴파일됩니다.

package main

여기서 task-api는 디렉터리 이름이고, main은 패키지 이름입니다.


package main의 의미

다음 선언에서 main은 디렉터리 이름이 아니라 패키지 이름입니다.

package main

Go 언어 명세에 따르면 실행 가능한 완전한 프로그램의 시작 패키지는 이름이 main이어야 하며, 인수와 반환값이 없는 main 함수도 선언해야 합니다.

package main

import "fmt"

func main() {
    fmt.Println("프로그램을 시작합니다.")
}

각 요소의 역할은 다음과 같습니다.

코드 의미
package main 이 파일이 실행 프로그램의 main 패키지에 속함
func main() 프로그램 실행이 시작되는 함수
fmt.Println(...) 표준 출력으로 문자열을 표시

실행:

go run .

출력:

프로그램을 시작합니다.

package main만 선언하고 func main()이 없으면 실행 가능한 프로그램을 완성할 수 없습니다.

예를 들어 다음 코드는 main 함수가 없으므로 실행 파일을 만들 때 오류가 발생합니다.

package main

func printMessage() {
    println("hello")
}

main이라는 이름은 실행 프로그램에서 특별한 의미를 가집니다. 일반적으로 다른 패키지에서 가져다 쓰는 라이브러리 패키지는 package main이 아닌 기능을 나타내는 이름을 사용합니다.


디렉터리와 패키지의 관계

Go에서 패키지는 기본적으로 같은 디렉터리에 있고 함께 컴파일되는 소스 파일들의 묶음입니다.

따라서 패키지를 판단할 때 패키지 이름만 보면 안 되고, 파일이 위치한 디렉터리도 함께 확인해야 합니다.

같은 디렉터리와 같은 패키지 이름

task-api/
├── main.go
├── config.go
└── server.go

main.go:

package main

import "fmt"

func main() {
    fmt.Println(loadConfig())
    startServer()
}

config.go:

package main

func loadConfig() string {
    return "설정을 불러왔습니다."
}

server.go:

package main

import "fmt"

func startServer() {
    fmt.Println("서버를 시작합니다.")
}

세 파일은 다음 조건을 만족합니다.

  • 같은 task-api 디렉터리에 있습니다.
  • 모두 package main으로 선언되어 있습니다.
  • 하나의 main 패키지로 함께 컴파일됩니다.

따라서 main.go에서 config.goloadConfig()server.gostartServer()를 별도의 import 없이 호출할 수 있습니다.

실행:

go run .

출력:

설정을 불러왔습니다.
서버를 시작합니다.

패키지 이름이 같아도 디렉터리가 다른 경우

project/
├── cmd/
│   └── api/
│       └── main.go
└── cmd/
    └── worker/
        └── main.go

cmd/api/main.go:

package main

func main() {
    println("API 실행")
}

cmd/worker/main.go:

package main

func main() {
    println("Worker 실행")
}

두 파일 모두 package main이지만 디렉터리가 다르므로 서로 다른 패키지이자 서로 다른 실행 프로그램입니다.

각각 다음과 같이 실행할 수 있습니다.

go run ./cmd/api
go run ./cmd/worker

즉, 다음 표현이 가장 정확합니다.

같은 디렉터리 + 같은 package 선언
= 일반적으로 하나의 패키지

패키지 이름만 같고 디렉터리가 다르면 같은 패키지가 아닙니다.


같은 패키지의 파일은 어떻게 동작하는가

같은 패키지에 속한 파일들은 함께 컴파일되므로 한 파일에 선언한 함수, 타입, 변수, 상수를 다른 파일에서 직접 사용할 수 있습니다.

함수와 타입 공유 예제

task-api/
├── main.go
├── task.go
└── status.go

task.go:

package main

type Task struct {
    Title string
}

func NewTask(title string) Task {
    return Task{Title: title}
}

status.go:

package main

const StatusReady = "READY"

func taskStatus() string {
    return StatusReady
}

main.go:

package main

import "fmt"

func main() {
    task := NewTask("Go 패키지 공부")

    fmt.Println(task.Title)
    fmt.Println(taskStatus())
}

실행:

go run .

출력:

Go 패키지 공부
READY

NewTask, Task, StatusReady, taskStatus는 서로 다른 파일에 있지만 모두 같은 main 패키지에 있으므로 직접 사용할 수 있습니다.

선언 이름은 중복할 수 없음

같은 패키지에 같은 이름의 함수 등을 중복 선언하면 컴파일 오류가 발생합니다.

first.go:

package main

func printMessage() {
    println("first")
}

second.go:

package main

func printMessage() {
    println("second")
}

두 파일은 같은 패키지 범위에 printMessage를 중복 선언하므로 정상적으로 컴파일되지 않습니다.


일반 패키지와 main 패키지 분리 예제

실제 프로젝트에서는 실행 진입점과 재사용할 기능을 서로 다른 패키지로 분리하는 경우가 많습니다.

task-api/
├── go.mod
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    └── message/
        └── message.go

go.mod:

module example.com/task-api

go 1.26.1

internal/message/message.go:

package message

func Hello(name string) string {
    return "안녕하세요, " + name + "님"
}

cmd/api/main.go:

package main

import (
    "fmt"

    "example.com/task-api/internal/message"
)

func main() {
    fmt.Println(message.Hello("Go"))
}

실행:

go run ./cmd/api

출력:

안녕하세요, Go님

이 구조에서 각 항목은 다음과 같습니다.

항목
모듈 경로 example.com/task-api
실행 패키지 위치 cmd/api
실행 패키지 이름 main
일반 패키지 위치 internal/message
일반 패키지 이름 message
import 경로 example.com/task-api/internal/message

message 패키지에서 이름이 대문자로 시작하는 Hello 함수는 다른 패키지에서 사용할 수 있습니다.

반대로 다음처럼 소문자로 시작하면 패키지 외부에서 사용할 수 없습니다.

package message

func hello(name string) string {
    return "안녕하세요, " + name + "님"
}

Go에서는 식별자의 첫 글자가 대문자이면 다른 패키지에 공개되고, 소문자이면 해당 패키지 내부에서만 사용할 수 있습니다.


같은 디렉터리에서 패키지 이름이 다른 경우

하나의 일반적인 소스 디렉터리 안에서는 Go 파일들이 같은 패키지 이름을 사용해야 합니다.

잘못된 예:

task-api/
├── main.go
└── message.go

main.go:

package main

message.go:

package message

같은 디렉터리에 mainmessage라는 서로 다른 패키지가 섞여 있으므로 빌드할 수 없습니다.

다음과 같이 디렉터리를 분리해야 합니다.

task-api/
├── main.go
└── message/
    └── message.go

다만 테스트 파일은 예외적으로 외부 테스트 패키지 형식을 사용할 수 있습니다.

calculator/
├── calculator.go
└── calculator_test.go

calculator.go:

package calculator

func Add(a, b int) int {
    return a + b
}

calculator_test.go:

package calculator_test

import (
    "testing"

    "example.com/project/calculator"
)

func TestAdd(t *testing.T) {
    result := calculator.Add(1, 2)

    if result != 3 {
        t.Fatalf("Add(1, 2) = %d, want 3", result)
    }
}

package calculator_test는 테스트 대상 패키지를 외부 사용자처럼 import하여 검증할 때 사용하는 특별한 테스트 구성입니다.


파일 단위가 아니라 패키지 단위로 실행하기

여러 파일이 하나의 패키지를 구성할 때는 다음 명령을 사용하는 것이 좋습니다.

go run .

이 명령은 현재 디렉터리의 패키지를 대상으로 빌드하고 실행합니다.

반면 다음 명령은 지정한 파일만 대상으로 실행을 시도합니다.

go run main.go

main.go가 다른 파일의 함수를 사용한다면 해당 파일이 포함되지 않아 오류가 발생할 수 있습니다.

예를 들어 main.go에서 config.goloadConfig()를 호출하는 구조라면 다음 명령은 문제가 될 수 있습니다.

go run main.go

패키지 전체를 실행하려면 다음 명령을 사용합니다.

go run .

필요하면 파일을 모두 직접 지정할 수도 있지만, 일반적으로는 패키지 단위 명령이 더 명확합니다.

go run main.go config.go server.go

빌드와 테스트도 패키지 기준으로 실행할 수 있습니다.

go build .
go test .

현재 모듈 아래의 모든 패키지를 대상으로 하려면 다음과 같이 실행합니다.

go test ./...

정리

package mainmain은 디렉터리명이 아니라 패키지 이름입니다.

main 패키지는 실행 가능한 Go 프로그램을 만들 때 사용하며, 실제 실행 시작점인 func main()이 필요합니다.

같은 패키지인지 판단할 때는 다음 기준을 사용하면 됩니다.

같은 디렉터리에 있고
빌드 조건에 포함되며
같은 package 이름을 선언한 Go 파일
= 함께 컴파일되는 하나의 패키지

핵심 내용을 다시 정리하면 다음과 같습니다.

  • package mainmain은 패키지 이름입니다.
  • 디렉터리 이름이 main일 필요는 없습니다.
  • 같은 디렉터리의 같은 패키지 파일은 하나의 패키지로 함께 컴파일됩니다.
  • 같은 패키지의 함수, 타입, 변수는 파일이 달라도 직접 사용할 수 있습니다.
  • 패키지 이름이 같더라도 디렉터리가 다르면 서로 다른 패키지입니다.
  • 실행 프로그램에는 package mainfunc main()이 필요합니다.
  • 여러 파일로 구성된 패키지는 일반적으로 go run .으로 실행합니다.

참고 자료

반응형

'프로그래밍 > Go' 카테고리의 다른 글

Go fmt의 %v와 %q  (0) 2026.07.11
Go 리터럴, 변수, 상수와 Zero Value  (0) 2026.07.10
Ubuntu 24.04에서 Go 1.26.1과 VS Code 설치하기  (1) 2026.07.03
Go 모듈과 패키지 이해하기  (0) 2026.07.03
VS Code Go 환경 세팅  (0) 2026.04.14

댓글