Skip to content

✍️ 필사 모드: Go 언어 완전 문법 가이드: 기초부터 동시성, 제네릭까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

소개

Go(Golang)는 Google이 2009년 발표한 정적 타입, 컴파일형 언어입니다. 단순한 문법, 빠른 컴파일, 강력한 동시성 지원, 풍부한 표준 라이브러리가 특징입니다. 클라우드 인프라, 마이크로서비스, CLI 도구 개발에 특히 강력합니다.

이 가이드는 Go의 핵심 문법을 처음부터 끝까지 체계적으로 정리합니다.


1. Go 언어 기초 문법

1.1 변수 선언

Go에서 변수를 선언하는 방법은 여러 가지입니다.

package main

import "fmt"

func main() {
    // var 키워드로 선언 (타입 명시)
    var name string = "Go"
    var age int = 15

    // 타입 추론 (var)
    var pi = 3.14159

    // 단축 선언 (:=) - 함수 내부에서만 사용 가능
    greeting := "Hello, Go!"
    count := 42

    // 복수 변수 선언
    var x, y int = 10, 20
    a, b := 100, 200

    // 제로값: 선언 시 초기화하지 않으면 타입의 기본값
    var zeroInt int       // 0
    var zeroStr string    // ""
    var zeroBool bool     // false

    fmt.Println(name, age, pi, greeting, count)
    fmt.Println(x, y, a, b)
    fmt.Println(zeroInt, zeroStr, zeroBool)
}

1.2 상수와 iota

package main

import "fmt"

// 단순 상수
const Pi = 3.14159
const MaxSize = 1024

// 상수 블록
const (
    StatusOK    = 200
    StatusNotFound = 404
    StatusError = 500
)

// iota: 열거형 상수 자동 증가
type Direction int

const (
    North Direction = iota // 0
    East                   // 1
    South                  // 2
    West                   // 3
)

type ByteSize float64

const (
    _           = iota // blank identifier로 0 건너뜀
    KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
    MB                              // 1 << 20
    GB                              // 1 << 30
)

func main() {
    fmt.Println(North, East, South, West) // 0 1 2 3
    fmt.Printf("KB=%.0f, MB=%.0f, GB=%.0f\n", float64(KB), float64(MB), float64(GB))
}

1.3 기본 타입

package main

import "fmt"

func main() {
    // 정수 타입
    var i8 int8 = 127
    var i16 int16 = 32767
    var i32 int32 = 2147483647
    var i64 int64 = 9223372036854775807
    var ui uint = 42
    var ui64 uint64 = 18446744073709551615

    // 플랫폼 의존 (64비트 시스템에서 int64)
    var n int = 100

    // 부동소수점
    var f32 float32 = 3.14
    var f64 float64 = 3.141592653589793

    // 복소수
    var c128 complex128 = 3 + 4i

    // 문자열 (UTF-8 인코딩, 불변)
    s := "Hello, 세계"
    fmt.Println(len(s))        // 바이트 수
    fmt.Println([]rune(s))     // 룬(유니코드 코드포인트) 슬라이스

    // byte (uint8의 별칭)
    var b byte = 'A'

    // rune (int32의 별칭, 유니코드 코드포인트)
    var r rune = '한'

    // bool
    var flag bool = true

    fmt.Println(i8, i16, i32, i64, ui, ui64, n)
    fmt.Println(f32, f64, c128)
    fmt.Println(b, r, flag)
}

1.4 배열과 슬라이스

package main

import "fmt"

func main() {
    // 배열: 고정 크기, 값 타입
    var arr [5]int
    arr[0] = 10
    arr2 := [3]string{"Go", "Python", "Rust"}
    arr3 := [...]int{1, 2, 3, 4, 5} // 컴파일러가 크기 추론

    fmt.Println(arr, arr2, arr3)

    // 슬라이스: 동적 크기, 참조 타입
    s1 := []int{1, 2, 3}
    s2 := make([]int, 3)       // len=3, cap=3
    s3 := make([]int, 3, 10)   // len=3, cap=10

    fmt.Println(len(s1), cap(s1)) // 3, 3
    fmt.Println(len(s2), cap(s2)) // 3, 3
    fmt.Println(len(s3), cap(s3)) // 3, 10

    // append: 용량 초과 시 새 배열 할당
    s1 = append(s1, 4, 5)
    fmt.Println(s1) // [1 2 3 4 5]

    // 슬라이싱
    s4 := s1[1:3]  // [2 3] (원본과 메모리 공유)
    s5 := s1[2:]   // [3 4 5]
    s6 := s1[:2]   // [1 2]

    fmt.Println(s4, s5, s6)

    // copy: 독립적인 복사본 생성
    dst := make([]int, len(s1))
    n := copy(dst, s1)
    fmt.Printf("복사된 원소 수: %d, dst: %v\n", n, dst)

    // 2차원 슬라이스
    matrix := make([][]int, 3)
    for i := range matrix {
        matrix[i] = make([]int, 3)
        for j := range matrix[i] {
            matrix[i][j] = i*3 + j
        }
    }
    fmt.Println(matrix)
}

1.5 맵

package main

import "fmt"

func main() {
    // 맵 선언과 초기화
    m1 := map[string]int{
        "apple":  5,
        "banana": 3,
        "cherry": 8,
    }

    m2 := make(map[string][]string)

    // 값 추가/수정
    m1["date"] = 12
    m2["fruits"] = []string{"apple", "banana"}

    // 값 읽기
    val := m1["apple"]
    fmt.Println(val) // 5

    // comma-ok 패턴: 키 존재 여부 확인
    val2, ok := m1["mango"]
    if !ok {
        fmt.Println("mango not found, zero value:", val2)
    }

    // 삭제
    delete(m1, "cherry")
    fmt.Println(m1)

    // 순회 (순서 보장 안 됨)
    for key, value := range m1 {
        fmt.Printf("%s: %d\n", key, value)
    }

    // nil 맵에 쓰기는 패닉 발생
    var m3 map[string]int
    // m3["key"] = 1 // panic: assignment to entry in nil map
    _ = m3
}

1.6 포인터

package main

import "fmt"

func increment(n *int) {
    *n++ // 역참조
}

func newInt(val int) *int {
    return &val // 로컬 변수의 주소 반환 가능 (힙 이동)
}

func main() {
    x := 42
    p := &x        // x의 주소
    fmt.Println(p)  // 메모리 주소 출력 (예: 0xc0000b4010)
    fmt.Println(*p) // 42 (역참조)

    *p = 100
    fmt.Println(x) // 100

    increment(&x)
    fmt.Println(x) // 101

    ptr := newInt(200)
    fmt.Println(*ptr) // 200

    // nil 포인터
    var nilPtr *int
    fmt.Println(nilPtr) // <nil>
    // fmt.Println(*nilPtr) // panic: nil pointer dereference
}

1.7 제어문

package main

import "fmt"

func main() {
    // for: Go의 유일한 반복문
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println()

    // while 스타일
    n := 1
    for n < 100 {
        n *= 2
    }
    fmt.Println(n)

    // range
    nums := []int{10, 20, 30, 40}
    for idx, val := range nums {
        fmt.Printf("[%d]=%d ", idx, val)
    }
    fmt.Println()

    // 인덱스만 사용
    for i := range nums {
        fmt.Print(nums[i], " ")
    }
    fmt.Println()

    // switch (break 없이도 fallthrough 안 됨)
    day := "Monday"
    switch day {
    case "Saturday", "Sunday":
        fmt.Println("주말")
    case "Monday":
        fmt.Println("월요일")
    default:
        fmt.Println("평일")
    }

    // 조건 없는 switch (if-else 대체)
    score := 85
    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }

    // defer: 함수 종료 시 실행 (LIFO 순서)
    fmt.Println("시작")
    defer fmt.Println("첫 번째 defer")
    defer fmt.Println("두 번째 defer")
    fmt.Println("끝")
    // 출력: 시작 -> 끝 -> 두 번째 defer -> 첫 번째 defer
}

2. 함수와 메서드

2.1 다중 반환값과 Named Return

package main

import (
    "errors"
    "fmt"
)

// 다중 반환값
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named return values
func minMax(nums []int) (min, max int) {
    if len(nums) == 0 {
        return // 제로값 반환
    }
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    return // naked return
}

// Variadic functions (가변 인수)
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Printf("%.4f\n", result) // 3.3333
    }

    _, err = divide(5, 0)
    if err != nil {
        fmt.Println("Error:", err) // division by zero
    }

    min, max := minMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
    fmt.Println(min, max) // 1 9

    fmt.Println(sum(1, 2, 3, 4, 5)) // 15

    // 슬라이스를 variadic 인수로 펼치기
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...)) // 60
}

2.2 일급 함수와 클로저

package main

import "fmt"

// 함수를 인수로 받기
func apply(nums []int, fn func(int) int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = fn(n)
    }
    return result
}

// 함수를 반환하기
func multiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}

// 클로저: 외부 변수를 캡처
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    double := func(n int) int { return n * 2 }
    nums := []int{1, 2, 3, 4, 5}

    fmt.Println(apply(nums, double))    // [2 4 6 8 10]
    fmt.Println(apply(nums, func(n int) int { return n * n })) // [1 4 9 16 25]

    triple := multiplier(3)
    fmt.Println(triple(7)) // 21

    c1 := counter()
    c2 := counter()
    fmt.Println(c1(), c1(), c1()) // 1 2 3
    fmt.Println(c2(), c2())       // 1 2 (독립적인 상태)
}

2.3 메서드 (Value Receiver vs Pointer Receiver)

package main

import (
    "fmt"
    "math"
)

type Circle struct {
    Radius float64
}

// Value receiver: 복사본에 적용 (원본 변경 불가)
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Pointer receiver: 원본 변경 가능
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}

type Rectangle struct {
    Width, Height float64
}

func (r *Rectangle) Area() float64 {
    return r.Width * r.Height
}

// init() 함수: 패키지 초기화 시 자동 실행
func init() {
    fmt.Println("패키지 초기화")
}

func main() {
    c := Circle{Radius: 5}
    fmt.Printf("넓이: %.2f\n", c.Area())
    fmt.Printf("둘레: %.2f\n", c.Perimeter())

    c.Scale(2)
    fmt.Printf("스케일 후 반지름: %.1f\n", c.Radius) // 10.0

    // 포인터 메서드를 값에서 호출 가능 (자동 주소 취득)
    c2 := Circle{Radius: 3}
    (&c2).Scale(1.5) // 자동으로 &c2로 변환
}

3. 구조체와 인터페이스

3.1 구조체와 임베딩

package main

import "fmt"

type Animal struct {
    Name string
    Age  int
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

// 임베딩: 상속이 아닌 구성(Composition)
type Dog struct {
    Animal              // 임베딩 (익명 필드)
    Breed  string
}

func (d Dog) Speak() string {
    return d.Name + " says: Woof!"
}

type ServiceDog struct {
    Dog
    ServiceType string
}

func main() {
    d := Dog{
        Animal: Animal{Name: "Buddy", Age: 3},
        Breed:  "Labrador",
    }

    // 임베딩된 필드 직접 접근
    fmt.Println(d.Name)  // d.Animal.Name과 동일
    fmt.Println(d.Age)
    fmt.Println(d.Breed)
    fmt.Println(d.Speak()) // Dog의 Speak() 호출
    fmt.Println(d.Animal.Speak()) // Animal의 Speak() 명시적 호출

    sd := ServiceDog{
        Dog:         d,
        ServiceType: "Guide",
    }
    fmt.Println(sd.Name)        // sd.Dog.Animal.Name
    fmt.Println(sd.ServiceType)
}

3.2 인터페이스

package main

import (
    "fmt"
    "math"
)

// 인터페이스 정의
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Stringer interface {
    String() string
}

type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }

func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (c Circle) String() string     { return fmt.Sprintf("Circle(r=%.1f)", c.Radius) }

func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func (r Rectangle) String() string {
    return fmt.Sprintf("Rect(%.1fx%.1f)", r.Width, r.Height)
}

// 인터페이스를 인수로 받는 함수
func printShapeInfo(s Shape) {
    fmt.Printf("넓이: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}

// 빈 인터페이스 (모든 타입)
func printAny(v interface{}) {
    fmt.Printf("타입: %T, 값: %v\n", v, v)
}

// 타입 어서션
func describe(i interface{}) {
    if s, ok := i.(string); ok {
        fmt.Printf("문자열: %q\n", s)
        return
    }
    if n, ok := i.(int); ok {
        fmt.Printf("정수: %d\n", n)
        return
    }
    fmt.Printf("알 수 없는 타입: %T\n", i)
}

// 타입 스위치
func typeSwitch(v interface{}) string {
    switch x := v.(type) {
    case int:
        return fmt.Sprintf("int: %d", x)
    case string:
        return fmt.Sprintf("string: %q", x)
    case bool:
        return fmt.Sprintf("bool: %v", x)
    case []int:
        return fmt.Sprintf("[]int with %d elements", len(x))
    default:
        return fmt.Sprintf("unknown type: %T", x)
    }
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 4, Height: 6},
    }

    for _, s := range shapes {
        printShapeInfo(s)
        if str, ok := s.(Stringer); ok {
            fmt.Println(str.String())
        }
    }

    printAny(42)
    printAny("hello")
    printAny([]int{1, 2, 3})

    describe("Go언어")
    describe(2024)

    fmt.Println(typeSwitch(100))
    fmt.Println(typeSwitch("world"))
    fmt.Println(typeSwitch([]int{1, 2}))
}

4. 에러 처리

4.1 에러 타입과 커스텀 에러

package main

import (
    "errors"
    "fmt"
)

// 센티널 에러 (패키지 수준 에러 변수)
var (
    ErrNotFound   = errors.New("not found")
    ErrPermission = errors.New("permission denied")
)

// 커스텀 에러 타입
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}

type DatabaseError struct {
    Code    int
    Message string
    Err     error // wrapped error
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("database error [%d]: %s", e.Code, e.Message)
}

func (e *DatabaseError) Unwrap() error {
    return e.Err
}

// fmt.Errorf로 에러 래핑
func findUser(id int) error {
    if id <= 0 {
        return fmt.Errorf("findUser: invalid id %d: %w", id, ErrNotFound)
    }
    return nil
}

func getUser(id int) error {
    if err := findUser(id); err != nil {
        return fmt.Errorf("getUser: %w", err)
    }
    return nil
}

func main() {
    // errors.Is: 에러 체인에서 특정 에러 확인
    err := getUser(-1)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("not found 에러:", err)
    }

    // errors.As: 에러 체인에서 특정 타입 추출
    dbErr := &DatabaseError{
        Code:    500,
        Message: "connection failed",
        Err:     ErrPermission,
    }
    wrapped := fmt.Errorf("service error: %w", dbErr)

    var de *DatabaseError
    if errors.As(wrapped, &de) {
        fmt.Printf("DB 에러 코드: %d\n", de.Code)
    }

    if errors.Is(wrapped, ErrPermission) {
        fmt.Println("권한 에러가 체인에 포함됨")
    }

    // 커스텀 에러 생성
    valErr := &ValidationError{Field: "email", Message: "invalid format"}
    fmt.Println(valErr)
}

4.2 panic과 recover

package main

import "fmt"

// recover로 panic 복구
func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    return a / b, nil
}

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("값은 양수여야 합니다: %d", n))
    }
    return n
}

func main() {
    result, err := safeDiv(10, 2)
    fmt.Printf("10/2 = %d, err = %v\n", result, err)

    result, err = safeDiv(10, 0)
    fmt.Printf("10/0 = %d, err = %v\n", result, err)

    // 정상적인 panic 사용: 프로그래밍 오류나 복구 불가능한 상황
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("패닉 복구:", r)
        }
    }()
    mustPositive(-5)
}

5. 고루틴과 채널 (동시성)

5.1 고루틴과 WaitGroup

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d 시작\n", id)
    time.Sleep(time.Millisecond * 100)
    fmt.Printf("Worker %d 완료\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("모든 워커 완료")
}

5.2 채널

package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int, count int) {
    for i := 0; i < count; i++ {
        ch <- i
        time.Sleep(time.Millisecond * 10)
    }
    close(ch)
}

func main() {
    // 언버퍼드 채널 (동기)
    ch1 := make(chan int)
    go func() { ch1 <- 42 }()
    fmt.Println(<-ch1) // 42

    // 버퍼드 채널 (비동기, 버퍼 차기 전까지 블록 안 됨)
    ch2 := make(chan string, 3)
    ch2 <- "first"
    ch2 <- "second"
    ch2 <- "third"
    fmt.Println(<-ch2) // first

    // 채널 순회
    numbers := make(chan int, 10)
    go producer(numbers, 5)

    for n := range numbers {
        fmt.Print(n, " ")
    }
    fmt.Println()

    // select 문: 여러 채널 동시 처리
    ch3 := make(chan string, 1)
    ch4 := make(chan string, 1)

    go func() {
        time.Sleep(time.Millisecond * 50)
        ch3 <- "채널3"
    }()
    go func() {
        time.Sleep(time.Millisecond * 30)
        ch4 <- "채널4"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch3:
            fmt.Println("수신:", msg)
        case msg := <-ch4:
            fmt.Println("수신:", msg)
        }
    }
}

5.3 Mutex와 동기화 프리미티브

package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.v[key]
}

// RWMutex: 읽기는 동시, 쓰기는 독점
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

// sync.Once: 한 번만 실행
var (
    instance *Cache
    once     sync.Once
)

func getInstance() *Cache {
    once.Do(func() {
        instance = &Cache{data: make(map[string]string)}
        fmt.Println("캐시 인스턴스 생성")
    })
    return instance
}

func main() {
    counter := SafeCounter{v: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Inc("key")
        }()
    }
    wg.Wait()
    fmt.Println("카운터:", counter.Value("key")) // 1000

    // sync.Map: 동시성 안전한 맵
    var sm sync.Map
    sm.Store("go", "1.21")
    sm.Store("python", "3.12")

    sm.Range(func(key, value interface{}) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true
    })

    c1 := getInstance()
    c2 := getInstance()
    fmt.Println(c1 == c2) // true (같은 인스턴스)
}

5.4 context.Context

package main

import (
    "context"
    "fmt"
    "time"
)

func doWork(ctx context.Context, id int) error {
    select {
    case <-time.After(time.Millisecond * 200):
        fmt.Printf("작업 %d 완료\n", id)
        return nil
    case <-ctx.Done():
        fmt.Printf("작업 %d 취소: %v\n", id, ctx.Err())
        return ctx.Err()
    }
}

func fetchData(ctx context.Context, url string) (string, error) {
    // 컨텍스트에서 값 읽기
    if requestID, ok := ctx.Value("requestID").(string); ok {
        fmt.Printf("요청 ID: %s, URL: %s\n", requestID, url)
    }
    select {
    case <-time.After(time.Millisecond * 100):
        return "data from " + url, nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

func main() {
    // WithCancel
    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        time.Sleep(time.Millisecond * 50)
        cancel() // 취소 신호 전송
    }()
    doWork(ctx, 1)

    // WithTimeout
    ctx2, cancel2 := context.WithTimeout(context.Background(), time.Millisecond*150)
    defer cancel2()
    doWork(ctx2, 2)

    // WithDeadline
    deadline := time.Now().Add(time.Millisecond * 300)
    ctx3, cancel3 := context.WithDeadline(context.Background(), deadline)
    defer cancel3()
    doWork(ctx3, 3)

    // WithValue: 요청 범위 데이터 전달
    ctx4 := context.WithValue(context.Background(), "requestID", "req-001")
    data, err := fetchData(ctx4, "https://api.example.com/users")
    if err == nil {
        fmt.Println("데이터:", data)
    }
}

6. 패키지와 모듈

6.1 Go Modules

# 새 모듈 초기화
go mod init github.com/username/myapp

# 의존성 추가
go get github.com/gin-gonic/gin@v1.9.1

# 의존성 정리
go mod tidy

# 벤더 디렉토리 생성
go mod vendor

go.mod 파일 예시:

module github.com/username/myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/stretchr/testify v1.8.4
)

6.2 패키지 구조 베스트 프랙티스

myapp/
├── cmd/
│   └── server/
│       └── main.go          # 진입점
├── internal/                 # 외부 패키지에서 임포트 불가
│   ├── handler/
│   │   └── user.go
│   ├── service/
│   │   └── user.go
│   └── repository/
│       └── user.go
├── pkg/                     # 외부에서 임포트 가능한 라이브러리
│   └── validator/
│       └── validator.go
├── api/                     # API 정의 (OpenAPI, Protobuf)
├── config/
│   └── config.go
├── go.mod
└── go.sum

6.3 주요 표준 라이브러리

package main

import (
    "fmt"
    "os"
    "strings"
    "strconv"
    "time"
    "io"
    "bytes"
)

func main() {
    // strings 패키지
    s := "  Hello, Go World!  "
    fmt.Println(strings.TrimSpace(s))
    fmt.Println(strings.ToUpper(s))
    fmt.Println(strings.Contains(s, "Go"))
    fmt.Println(strings.Replace(s, "Go", "Golang", 1))
    parts := strings.Split("a,b,c,d", ",")
    fmt.Println(parts)
    fmt.Println(strings.Join(parts, " | "))

    // strconv 패키지
    n, _ := strconv.Atoi("42")
    fmt.Println(n + 1)
    str := strconv.Itoa(100)
    fmt.Println(str + " bottles")
    f, _ := strconv.ParseFloat("3.14", 64)
    fmt.Printf("%.2f\n", f)

    // time 패키지
    now := time.Now()
    fmt.Println(now.Format("2006-01-02 15:04:05"))
    fmt.Println(now.Format(time.RFC3339))
    tomorrow := now.Add(24 * time.Hour)
    fmt.Println(tomorrow.Sub(now))

    t, _ := time.Parse("2006-01-02", "2026-03-17")
    fmt.Println(t.Year(), t.Month(), t.Day())

    // os 패키지
    fmt.Println(os.Getenv("HOME"))
    args := os.Args // 커맨드라인 인수

    // io와 bytes
    buf := bytes.NewBufferString("Hello")
    buf.WriteString(", World")
    data, _ := io.ReadAll(buf)
    fmt.Println(string(data))

    _ = args
}

7. 제네릭 (Go 1.18+)

7.1 제네릭 함수와 타입

package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// 타입 파라미터를 사용한 제네릭 함수
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, pred func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if pred(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    acc := initial
    for _, v := range slice {
        acc = fn(acc, v)
    }
    return acc
}

// 제네릭 타입
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    top := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return top, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// comparable 제약: == 연산자 사용 가능
func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

// 커스텀 타입 제약
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println(Min(3, 5))         // 3
    fmt.Println(Min(3.14, 2.71))   // 2.71
    fmt.Println(Min("apple", "banana")) // apple

    nums := []int{1, 2, 3, 4, 5}
    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println(doubled) // [2 4 6 8 10]

    strs := Map(nums, func(n int) string { return fmt.Sprintf("item_%d", n) })
    fmt.Println(strs)

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4]

    total := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(total) // 15

    var s Stack[string]
    s.Push("first")
    s.Push("second")
    s.Push("third")

    for s.Len() > 0 {
        item, _ := s.Pop()
        fmt.Println(item)
    }

    fmt.Println(Contains([]string{"go", "python", "rust"}, "go")) // true
    fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}

8. 코드 예시: REST API 서버 (net/http)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type UserStore struct {
    mu      sync.RWMutex
    users   map[int]User
    nextID  int
}

func NewUserStore() *UserStore {
    return &UserStore{
        users:  make(map[int]User),
        nextID: 1,
    }
}

func (s *UserStore) Create(name, email string) User {
    s.mu.Lock()
    defer s.mu.Unlock()
    user := User{ID: s.nextID, Name: name, Email: email}
    s.users[s.nextID] = user
    s.nextID++
    return user
}

func (s *UserStore) GetAll() []User {
    s.mu.RLock()
    defer s.mu.RUnlock()
    users := make([]User, 0, len(s.users))
    for _, u := range s.users {
        users = append(users, u)
    }
    return users
}

func (s *UserStore) GetByID(id int) (User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    u, ok := s.users[id]
    return u, ok
}

// JSON 응답 헬퍼
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

// 핸들러 팩토리 (클로저로 의존성 주입)
func makeUsersHandler(store *UserStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            users := store.GetAll()
            writeJSON(w, http.StatusOK, users)

        case http.MethodPost:
            var body struct {
                Name  string `json:"name"`
                Email string `json:"email"`
            }
            if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
                writeJSON(w, http.StatusBadRequest, map[string]string{
                    "error": "invalid request body",
                })
                return
            }
            user := store.Create(body.Name, body.Email)
            writeJSON(w, http.StatusCreated, user)

        default:
            http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
        }
    }
}

func makeUserHandler(store *UserStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // URL에서 ID 추출: /api/users/123
        parts := strings.Split(r.URL.Path, "/")
        if len(parts) < 4 {
            http.Error(w, "Bad Request", http.StatusBadRequest)
            return
        }
        id, err := strconv.Atoi(parts[3])
        if err != nil {
            http.Error(w, "Invalid ID", http.StatusBadRequest)
            return
        }

        user, ok := store.GetByID(id)
        if !ok {
            writeJSON(w, http.StatusNotFound, map[string]string{
                "error": fmt.Sprintf("user %d not found", id),
            })
            return
        }
        writeJSON(w, http.StatusOK, user)
    }
}

// 미들웨어: 로깅
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func main() {
    store := NewUserStore()

    // 초기 데이터
    store.Create("Alice", "alice@example.com")
    store.Create("Bob", "bob@example.com")

    mux := http.NewServeMux()
    mux.Handle("/api/users", makeUsersHandler(store))
    mux.Handle("/api/users/", makeUserHandler(store))
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
    })

    handler := loggingMiddleware(mux)

    addr := ":8080"
    fmt.Printf("서버 시작: http://localhost%s\n", addr)
    log.Fatal(http.ListenAndServe(addr, handler))
}

9. 퀴즈

퀴즈 1: 슬라이스 동작 이해

다음 코드의 출력 결과는?

a := []int{1, 2, 3, 4, 5}
b := a[1:3]
b[0] = 99
fmt.Println(a)

정답: [1 99 3 4 5]

설명: 슬라이싱으로 만든 ba와 메모리를 공유합니다. b[0]a[1]과 같은 메모리 위치를 가리키므로 b[0] = 99를 하면 a[1]도 99로 변경됩니다.

퀴즈 2: 고루틴과 클로저 함정

다음 코드는 왜 문제가 있고 어떻게 수정해야 하나요?

for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i)
    }()
}
time.Sleep(time.Second)

정답: 대부분 5를 5번 출력하거나 예측할 수 없는 값을 출력합니다.

설명: 클로저가 변수 i를 캡처할 때 값이 아닌 참조를 캡처합니다. 고루틴이 실행될 때 이미 반복문이 끝나 i가 5가 됩니다. 수정 방법은 두 가지입니다.

1번 방법 - 인수로 전달:

for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

2번 방법 - 지역 변수 생성:

for i := 0; i < 5; i++ {
    i := i // 새 변수 i 생성
    go func() {
        fmt.Println(i)
    }()
}
퀴즈 3: 인터페이스와 nil

다음 코드에서 isNil의 반환값은?

type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }

func getError() error {
    var err *MyError = nil
    return err
}

func main() {
    err := getError()
    fmt.Println(err == nil)
}

정답: false

설명: Go의 인터페이스는 타입과 값 두 가지 구성요소를 가집니다. getError()*MyError 타입에 nil 값인 인터페이스를 반환합니다. 이 인터페이스는 타입 정보(*MyError)를 가지고 있으므로 error 인터페이스와 비교할 때 nil이 아닙니다. nil error를 올바르게 반환하려면 return nil로 직접 반환해야 합니다.

퀴즈 4: defer 실행 순서

다음 코드의 출력 순서는?

func main() {
    fmt.Println("A")
    defer fmt.Println("defer 1")
    fmt.Println("B")
    defer fmt.Println("defer 2")
    fmt.Println("C")
    defer fmt.Println("defer 3")
    fmt.Println("D")
}

정답: A B C D defer 3 defer 2 defer 1

설명: defer는 LIFO(Last In, First Out) 스택 방식으로 동작합니다. 일반 문장은 선언 순서대로 실행되고, defer된 함수들은 함수 종료 시 역순(나중에 defer된 것이 먼저)으로 실행됩니다.

퀴즈 5: 채널 방향성

다음 함수 시그니처에서 ch chan<- intch <-chan int의 차이는?

정답:

  • chan<- int: 송신 전용 채널 (쓰기만 가능)
  • <-chan int: 수신 전용 채널 (읽기만 가능)

설명: Go는 채널의 방향을 타입으로 제한할 수 있습니다. 이를 통해 함수가 채널을 어떻게 사용하는지 컴파일 타임에 검증할 수 있습니다. 예시:

func send(ch chan<- int, val int) {
    ch <- val  // OK
    // <-ch    // 컴파일 오류
}

func receive(ch <-chan int) int {
    return <-ch  // OK
    // ch <- 1   // 컴파일 오류
}

func pipe(in <-chan int, out chan<- int) {
    for v := range in {
        out <- v * 2
    }
}

채널 방향을 명시하면 실수로 잘못된 방향으로 채널을 사용하는 것을 방지합니다.

현재 단락 (1/1137)

Go(Golang)는 Google이 2009년 발표한 정적 타입, 컴파일형 언어입니다. 단순한 문법, 빠른 컴파일, 강력한 동시성 지원, 풍부한 표준 라이브러리가 특징입니다. 클라...

작성 글자: 0원문 글자: 21,014작성 단락: 0/1137