Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

소개

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

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

1. Go 언어 기초 문법

1.1 변수 선언

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

package main

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

// 단순 상수

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

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

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

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

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

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

"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

// 함수를 인수로 받기

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

"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

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

"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

"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

// 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

"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

"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

"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

"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

"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

"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

"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. 퀴즈

다음 코드의 출력 결과는?

a := []int{1, 2, 3, 4, 5}

b := a[1:3]

b[0] = 99

fmt.Println(a)

**정답**: `[1 99 3 4 5]`

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

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

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)

}()

}

다음 코드에서 `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`로 직접 반환해야 합니다.

다음 코드의 출력 순서는?

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된 것이 먼저)으로 실행됩니다.

다음 함수 시그니처에서 `ch chan<- int`와 `ch <-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/1122)

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

작성 글자: 0원문 글자: 20,764작성 단락: 0/1122