Hello, World! — 첫 번째 Go 프로그램
Go 개발 환경이 준비되었다면, 이제 첫 번째 프로그램을 작성할 차례입니다. 단순한 Hello World 출력을 넘어, Go 프로그램의 구조, 패키지 시스템, 빌드 방법, import 심화까지 깊이 있게 다루겠습니다.
Hello, World! 전체 코드 분석
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
단 5줄의 코드이지만, Go의 핵심 개념이 모두 담겨 있습니다. 한 줄씩 분석해 보겠습니다.
package main
모든 Go 파일의 첫 줄은 반드시 패키지 선언 입니다.
- Go 프로그램은 패키지 단위로 구성됩니다
main패키지는 특별합니다 — 실행 가능한(executable) 프로그램의 진입점임을 Go 컴파일러에 알립니다- 라이브러리 패키지는
package mylib처럼 다른 이름을 사용합니다 - 같은 디렉토리 내의 모든
.go파일은 동일한 패키지에 속해야 합니다
import "fmt"
표준 라이브러리의 fmt 패키지를 가져옵니다.
fmt는 "format"의 약자로, 포매팅 및 입출력을 담당합니다- Go는 미사용 import를 컴파일 오류 로 처리합니다 — 불필요한 의존성을 방지하는 강력한 규칙입니다
- import 경로는 문자열 리터럴로 표현됩니다
func main()
func키워드로 함수를 선언합니다main()함수는main패키지에서 프로그램 시작점(entry point)입니다- 매개변수와 반환값이 없습니다
- 명령줄 인수는
os.Args로 접근합니다
fmt.Println("Hello, World!")
fmt패키지의Println함수 호출- 문자열을 출력하고 자동으로 줄바꿈(
\n)을 추가합니다 fmt.Print(줄바꿈 없음),fmt.Printf(포매팅) 도 자주 사용합니다
파일 생성 및 실행
# 프로젝트 디렉토리 생성
mkdir hello-go && cd hello-go
go mod init github.com/username/hello-go
# 파일 생성
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
fmt.Printf("Go 버전으로 출력: %s\n", "1.24")
fmt.Print("줄바꿈 없이 출력")
fmt.Print(" — 이어서 출력\n")
}
EOF
go run vs go build
go run— 컴파일 + 실행을 한 번에 (개발 중 빠른 테스트용):
go run main.go
# 출력:
# Hello, World!
# Go 버전으로 출력: 1.24
# 줄바꿈 없이 출력 — 이어서 출력
go run은 임시 디렉토리에 바이너리를 생성하고 즉시 실행한 뒤 삭제합니다.
go build— 바이너리 파일 생성:
# 현재 디렉토리에 바이너리 빌드 (기본 이름: 모듈명 또는 디렉토리명)
go build
# 출력 파일 이름 지정
go build -o hello ./...
# 빌드 후 실행
./hello
차이점 정리:
| 항목 | go run | go build |
|---|---|---|
| 용도 | 개발 중 즉시 테스트 | 배포용 바이너리 생성 |
| 속도 | 빠름 (임시 빌드) | 약간 느림 |
| 바이너리 저장 | 저장 안 함 | 지정된 경로에 저장 |
| 디버깅 심볼 | 포함 | 기본 포함 (-ldflags="-s -w" 로 제거 가능) |
크로스 컴파일
Go의 강력한 기능 중 하나입니다. 현재 개발 환경과 다른 OS/아키텍처용 바이너리를 단일 명령으로 빌드합니다.
# Linux AMD64용 빌드 (macOS나 Windows에서도 가능)
GOOS=linux GOARCH=amd64 go build -o hello-linux ./...
# Windows 64비트용 빌드
GOOS=windows GOARCH=amd64 go build -o hello.exe ./...
# macOS Apple Silicon용 빌드
GOOS=darwin GOARCH=arm64 go build -o hello-mac-arm ./...
# 빌드 정보 포함 (버전 임베딩)
go build -ldflags="-X main.version=1.0.0" -o hello ./...
지원하는 GOOS/GOARCH 조합 확인:
go tool dist list
import 심화
단일 vs 그룹 import
// 단일 import
import "fmt"
import "os"
import "strings"
// 그룹 import (권장 — goimports가 자동 정렬)
import (
"fmt"
"os"
"strings"
)
Go 커뮤니티 관례는 다음 순서로 import를 그룹화합니다:
import (
// 1. 표준 라이브러리
"fmt"
"os"
"strings"
// 2. 외부 패키지 (blank line으로 구분)
"github.com/gin-gonic/gin"
"go.uber.org/zap"
// 3. 내부 패키지 (blank line으로 구분)
"github.com/username/myapp/internal/config"
"github.com/username/myapp/pkg/database"
)
별칭(Alias) import
패키지 이름이 충돌하거나 너무 길 때 별칭을 사용합니다:
package main
import (
"fmt"
fm "fmt" // 별칭: fm으로 사용 (실제로는 비권장)
myfmt "fmt" // 또 다른 별칭 예시
)
func main() {
fmt.Println("fmt 사용")
fm.Println("별칭 fm으로도 동일하게 사용")
}
실제로 별칭이 유용한 경우:
import (
"crypto/rand"
mrand "math/rand" // 두 rand 패키지 동시 사용
)
// 이제 rand와 mrand로 구분
bytes := make([]byte, 16)
rand.Read(bytes) // crypto/rand
n := mrand.Intn(100) // math/rand
공백 import (_)
패키지의 init() 함수를 실행하되, 패키지 이름을 직접 사용하지 않을 때 씁니다. 주로 드라이버 등록에 활용됩니다:
import (
"database/sql"
_ "github.com/lib/pq" // PostgreSQL 드라이버 등록
_ "github.com/go-sql-driver/mysql" // MySQL 드라이버 등록
)
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
// pq 패키지를 직접 쓰지 않아도 드라이버가 등록됨
_ = db
_ = err
}
점 import (.) — 사용하지 마세요
import . "fmt" // fmt의 모든 공개 이름을 현재 스코프로 가져옴
func main() {
Println("fmt. 없이 사용") // fmt.Println 대신
}
점 import는 코드를 읽을 때 어느 패키지에서 온 것인지 알 수 없어 가독성을 해치므로 테스트 코드 외에는 사용하지 않는 것이 원칙 입니다.
여러 파일로 구성된 패키지
실제 프로젝트에서는 하나의 패키지를 여러 파일로 나눕니다.
디렉토리 구조:
hello-go/
go.mod
main.go
greet.go
greet.go:
package main
import "fmt"
// Greet는 이름을 받아 인사말을 출력합니다.
// 공개 함수는 대문자로 시작합니다.
func Greet(name string) {
fmt.Printf("안녕하세요, %s님!\n", name)
}
// greetLowercase는 패키지 내부에서만 사용하는 비공개 함수입니다.
func greetLowercase(name string) {
fmt.Printf("hello, %s\n", name)
}
main.go:
package main
import "fmt"
func main() {
fmt.Println("=== 인사말 프로그램 ===")
Greet("Go 학습자") // greet.go에 정의된 함수 호출
greetLowercase("내부 호출") // 같은 패키지이므로 접근 가능
}
# 두 파일을 모두 컴파일하여 실행
go run main.go greet.go
# 또는 패키지 전체 실행 (권장)
go run .
출력:
=== 인사말 프로그램 ===
안녕하세요, Go 학습자님!
hello, 내부 호출
Go의 공개/비공개 규칙:
- 대문자로 시작: 패키지 외부에서 접근 가능 (exported)
- 소문자로 시작: 패키지 내부에서만 접근 가능 (unexported)
go.mod와 go.sum 역할
hello-go/
go.mod ← 모듈 정의 + 의존성 목록
go.sum ← 의존성 체크섬 (보안 검증)
go.mod: 프로젝트의 ID와 의존성 선언
module github.com/username/hello-go
go 1.24
require (
github.com/fatih/color v1.16.0
)
go.sum: 각 의존성의 SHA-256 해시 (변조 방지)
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj/K049bybjyNeximgwfLJ+3X4MoO5Bi+5I=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
외부 패키지 사용하기
github.com/fatih/color 패키지로 컬러 출력 예제를 만들어 봅시다.
# 패키지 추가
go get github.com/fatih/color@v1.16.0
main.go:
package main
import (
"fmt"
"github.com/fatih/color"
)
func main() {
// 기본 컬러 출력
color.Red("에러: 파일을 찾을 수 없습니다")
color.Green("성공: 서버가 시작되었습니다")
color.Yellow("경고: 디스크 용량이 부족합니다")
color.Cyan("정보: 현재 버전은 1.24입니다")
color.Blue("디버그: 연결 시도 중...")
// 커스텀 스타일
bold := color.New(color.Bold)
bold.Println("굵은 텍스트")
boldRed := color.New(color.FgRed, color.Bold)
boldRed.Printf("중요 오류: %s\n", "데이터베이스 연결 실패")
// fmt와 혼용
fmt.Println(color.GreenString("✓ 테스트 통과"))
}
# 의존성 정리 후 실행
go mod tidy
go run .
프로그램 종료 코드
Go 프로그램은 main() 함수가 정상적으로 종료되면 종료 코드 0을 반환합니다. 비정상 종료를 나타내려면 os.Exit을 사용합니다.
package main
import (
"fmt"
"os"
)
func main() {
args := os.Args // os.Args[0]은 프로그램 이름, [1]부터 인수
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "사용법: hello <이름>")
os.Exit(1) // 비정상 종료 (0이 아닌 값)
}
name := args[1]
fmt.Printf("안녕하세요, %s!\n", name)
// os.Exit(0) — 정상 종료 (생략 가능)
}
go run main.go
# 출력: 사용법: hello <이름>
# 종료 코드: 1
go run main.go "Go개발자"
# 출력: 안녕하세요, Go개발자!
# 종료 코드: 0
# 종료 코드 확인
echo $?
주의: os.Exit은 defer된 함수를 실행하지 않으므로, 정리(cleanup) 로직이 있다면 defer를 사용하기 전에 호출해야 합니다.
실전 예제: 간단한 HTTP 서버
표준 라이브러리만으로 HTTP 서버를 만들 수 있습니다.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
fmt.Fprintf(w, "Hello, %s! 현재 시각: %s\n", name, time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Go HTTP 서버에 오신 것을 환영합니다!")
fmt.Fprintln(w, "사용법: /hello?name=이름")
})
port := ":8080"
log.Printf("서버 시작: http://localhost%s\n", port)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal("서버 오류:", err)
}
}
go run main.go
# 브라우저에서 http://localhost:8080/hello?name=Gopher 접속
외부 패키지 없이 이 수준의 HTTP 서버를 작성할 수 있다는 것이 Go 표준 라이브러리의 강점입니다.
정리
이 섹션에서 다룬 내용:
package main: 실행 가능 프로그램의 진입점 패키지import: 단일, 그룹, 별칭, 공백, 점 import 방식과 용도go runvsgo build: 개발 중 테스트 vs 배포용 바이너리- 크로스 컴파일:
GOOS/GOARCH로 다른 플랫폼용 빌드 - 여러 파일 패키지: 대문자/소문자로 결정되는 공개/비공개 규칙
- 외부 패키지 사용:
go get,go.mod,go.sum연동 os.Exit: 종료 코드 제어
다음 챕터에서는 Go의 변수, 타입, 상수를 깊이 있게 배워봅니다.