코딩 · 프로그래밍 언어
Go 언어 기초 입문 튜토리얼
핵심 답변
Go(흔히 Golang이라고도 부름)는 Google이 만든 단순한 정적 타입 컴파일 언어로, 빠른 빌드, 쉬운 동시성, 안정적인 서버를 목표로 합니다. Go를 설치하고 main 패키지를 작성한 뒤 go run hello.go로 실행합니다. 핵심 개념은 적은 수의 자료형, 슬라이스와 맵, 메서드를 가진 구조체, 암묵적 인터페이스, 동시성을 위한 고루틴과 채널, 그리고 일반 값으로 반환되는 오류입니다.
Go 언어란 무엇이고 어디에 쓰이는가
Go는 Google에서 로버트 그리즈머(Robert Griesemer), 롭 파이크(Rob Pike), 켄 톰프슨(Ken Thompson)이 설계해 2009년에 오픈 소스로 공개했습니다. 하나의 정적 바이너리로 컴파일되고, 시작이 빠르며, 가비지 컬렉터와 네트워킹용 풍부한 표준 라이브러리를 갖추고 있습니다. 클라우드 인프라와 명령줄 도구에 쓰이며, Docker, Kubernetes, Terraform, Prometheus가 Go로 작성되었고 많은 회사가 웹 API와 마이크로서비스에도 사용합니다. 언어가 의도적으로 작아서 팀이 달라도 코드가 비슷하게 보입니다.
Go 설치와 Hello, world 실행
go.dev/dl에서 Go를 내려받고 go version으로 확인하세요. 폴더를 만들고 모듈을 시작한 뒤 main.go를 추가합니다.
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}go mod init example.com/hello
go run .
# Hello, world
go build # produces a standalone binary named hello실행은 package main의 func main에서 시작합니다. 코드 형식은 gofmt(또는 go fmt)로 맞추세요. 모든 Go 코드는 같은 스타일을 쓰며, 실제 소스 파일에서는 들여쓰기에 탭을 사용합니다.
변수와 기본 자료형
변수는 var name type = value로 선언하거나, 함수 안에서는 타입을 추론하는 짧은 형식 name := value로 선언합니다. 값을 대입하지 않은 변수는 제로 값(0, "", false, nil)을 가집니다.
| 타입 | 예시 | 제로 값 |
|---|---|---|
int, int64 | n := 42 | 0 |
float64 | x := 3.14 | 0 |
bool | ok := true | false |
string | s := "hi" | "" |
rune | r := 'é' | 0 (유니코드 코드 포인트) |
byte | b := byte(65) | 0 (uint8의 별칭) |
package main
import (
"fmt"
"strconv"
)
func main() {
name := "Ada"
count := 7
avg := float64(count) / 2 // explicit conversion is required
fmt.Printf("%s has %d items, avg %.1f\n", name, count, avg) // Ada has 7 items, avg 3.5
n, err := strconv.Atoi("12")
fmt.Println(n+1, err) // 13 <nil>
}Go는 숫자 타입 사이를 암묵적으로 변환하지 않으며, 사용하지 않는 변수와 import는 컴파일러가 거부합니다.
제어 흐름과 함수
조건식에는 괄호가 없고 중괄호는 필수입니다. 반복문 키워드는 for 하나뿐이며 전통적인 형태, while 형태, range 형태를 모두 담당합니다. 함수는 여러 값을 반환할 수 있습니다.
package main
import "fmt"
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
score := 72
if score >= 90 {
fmt.Println("A")
} else if score >= 70 {
fmt.Println("B") // B
} else {
fmt.Println("C")
}
for i := 0; i < 3; i++ {
fmt.Print(i, " ") // 0 1 2
}
fmt.Println()
n := 10
for n > 1 { // while-style loop
n /= 2
}
q, r := divmod(17, 5)
fmt.Println(n, q, r) // 1 3 2
switch day := "SAT"; day {
case "SAT", "SUN":
fmt.Println("weekend") // no break needed
default:
fmt.Println("weekday")
}
}슬라이스, 맵, 구조체
슬라이스는 배열을 가리키는 늘어나는 뷰로, 일상적으로 쓰는 리스트 타입입니다. 맵은 해시 테이블입니다. 구조체는 이름 있는 필드를 묶고, 메서드는 리시버를 가진 함수입니다.
package main
import (
"fmt"
"sort"
)
type Point struct {
X, Y int
}
func (p Point) Add(o Point) Point { return Point{p.X + o.X, p.Y + o.Y} }
func (p *Point) Scale(k int) { p.X *= k; p.Y *= k } // pointer receiver modifies p
func main() {
nums := []int{3, 1, 2}
nums = append(nums, 5)
sort.Ints(nums)
fmt.Println(nums, len(nums), nums[1:3]) // [1 2 3 5] 4 [2 3]
ages := map[string]int{"ann": 31, "bob": 25}
ages["cy"] = 40
age, ok := ages["zed"]
fmt.Println(age, ok, len(ages)) // 0 false 3
for i, n := range nums {
fmt.Print(i, ":", n, " ") // 0:1 1:2 2:3 3:5
}
fmt.Println()
p := Point{1, 2}.Add(Point{3, 4})
p.Scale(2)
fmt.Printf("%+v\n", p) // {X:8 Y:12}
}맵의 순회 순서는 의도적으로 무작위입니다. 안정적인 순서가 필요하면 키를 정렬하세요. 대문자로 시작하는 이름(Point, Add)은 패키지 밖으로 공개됩니다.
인터페이스
인터페이스는 메서드 시그니처의 목록입니다. 타입이 그 메서드를 가지고 있기만 하면 인터페이스를 만족하며, implements 키워드는 없습니다.
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
shapes := []Shape{Circle{1}, Rect{2, 3}}
total := 0.0
for _, s := range shapes {
total += s.Area()
}
fmt.Printf("%.2f\n", total) // 9.14
}Go 1.18부터 제네릭도 있습니다. 예: func Max[T int | float64](a, b T) T. 주로 slices와 maps 패키지 같은 라이브러리 함수를 통해 사용하게 됩니다.
대표 기능: 고루틴과 채널
고루틴은 go 키워드로 시작하는 경량 스레드이며 프로그램 하나에서 수천 개를 실행할 수 있습니다. 고루틴끼리는 채널을 통해 통신하고, 채널은 값을 안전하게 전달합니다. sync.WaitGroup은 고루틴 그룹이 끝나기를 기다립니다.
package main
import (
"fmt"
"sync"
)
func square(n int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
results <- n * n
}
func main() {
results := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go square(i, results, &wg)
}
wg.Wait()
close(results)
sum := 0
for r := range results {
sum += r
}
fmt.Println(sum) // 55
}defer는 감싸는 함수가 반환될 때 실행할 호출을 예약하며, Go 코드가 파일을 닫고 뮤텍스를 해제하는 방법입니다. 개발 중에는 go run -race .로 데이터 레이스를 감지하세요.
오류 처리
Go에는 일반적인 실패를 위한 예외가 없습니다. 실패할 수 있는 함수는 마지막 결과로 error를 반환하고, 호출자가 바로 확인합니다. %w로 오류를 감싸 맥락을 더하면서도 errors.Is로 원래 오류를 확인할 수 있습니다.
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
return data, nil
}
func main() {
_, err := readConfig("missing.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("no config file:", err)
}
}panic과 recover가 있지만 프로그래밍 오류나 정말 예상치 못한 상태를 위한 것이며, 일반적인 오류 처리에는 쓰지 않습니다.
작은 완성 프로그램: 간단한 웹 서버
표준 라이브러리만으로 HTTP를 통해 JSON을 제공하는 프로그램입니다. go run .으로 실행하고 http://localhost:8080/hello?name=Ann을 여세요.
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Greeting struct {
Message string `json:"message"`
Time time.Time `json:"time"`
}
func hello(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "world"
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(Greeting{"Hello, " + name, time.Now()})
if err != nil {
log.Println("encode:", err)
}
}
func main() {
http.HandleFunc("/hello", hello)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}각 필드 타입 뒤의 텍스트는 JSON 키 이름을 정하는 구조체 태그입니다. 각 요청은 자동으로 별도의 고루틴에서 처리됩니다.
초보자가 자주 하는 실수
_로 오류를 무시하는 경우. 문제가 없다고 확신하지 않는 한 반환된 모든error를 확인하세요.append가 원본 슬라이스를 직접 바꿀 것으로 기대하는 경우. 항상s = append(s, x)처럼 결과를 사용하세요.- 고루틴을 시작해 놓고 끝나기 전에
main이 반환되게 두는 경우.sync.WaitGroup이나 채널로 기다리세요. - 메서드가 구조체를 수정해야 하는데 값 리시버를 쓰는 경우.
func (p *Point)처럼 포인터 리시버를 사용하세요.
다음 학습 단계
대화형 Tour of Go를 해 본 뒤 Effective Go와 pkg.go.dev의 표준 라이브러리 문서를 읽으세요. 단위 테스트와 벤치마크를 위한 go test, 타임아웃과 취소를 위한 context 패키지, 의존성을 위한 모듈을 익히세요.
자주 묻는 질문
Go라고 부르나요, Golang이라고 부르나요?
공식 이름은 Go입니다. "Golang"은 예전 웹사이트 주소 golang.org에서 유래했으며 지금도 검색어로 쓰입니다.
Go는 객체지향 언어인가요?
부분적으로 그렇습니다. Go에는 메서드와 인터페이스가 있지만 클래스와 상속은 없습니다. 구조체를 임베딩하고 작은 인터페이스를 조합해 타입을 만듭니다.
Go는 파이썬보다 빠른가요?
대부분의 CPU 중심 작업에서는 그렇습니다. Go는 네이티브 기계어로 컴파일되고 정적 타입을 쓰기 때문입니다. 다만 무거운 작업이 C로 작성된 라이브러리 안에서 일어난다면 파이썬도 빠를 수 있습니다.
고루틴과 스레드는 무엇이 다른가요?
고루틴은 Go 런타임이 관리하며 작은 스택으로 시작해 필요에 따라 늘어납니다. 런타임이 많은 고루틴을 소수의 운영체제 스레드에 스케줄링하므로 고루틴은 훨씬 적은 비용으로 만들 수 있습니다.