10 minute read

개요

상수(constant)는 컴파일 타임에 값이 결정되는 불변 식별자입니다.

주요 특징:

  • const 키워드로 선언
  • 선언과 동시에 초기화 필수
  • := 단축 선언 불가
  • 컴파일 타임 상수 표현식만 허용
  • 타입 있는 상수와 타입 없는 상수 지원
  • iota를 통한 자동 증가 열거형

기본 상수 선언

1. 단일 상수

package main

import "fmt"

func main() {
    // 타입 명시
    const pi float64 = 3.14159
    const maxRetries int = 5
    
    // 타입 추론
    const name = "Go"         // string으로 추론
    const version = 1.22      // float64로 추론
    const enabled = true      // bool로 추론
    
    fmt.Println(pi, maxRetries)
    fmt.Println(name, version, enabled)
    
    // ❌ 재할당 불가
    // pi = 3.14 // 컴파일 에러: cannot assign to pi
    
    // ❌ := 사용 불가
    // const value := 10 // 컴파일 에러
}

2. 상수 그룹

const (
    StatusOK       = 200
    StatusCreated  = 201
    StatusAccepted = 202
)

// 여러 줄 선언
const (
    host    = "localhost"
    port    = 8080
    timeout = 30
)

// 혼합 타입
const (
    appName    = "MyApp"
    appVersion = 1.0
    debug      = true
)

3. 타입 있는 상수 vs 타입 없는 상수

func typedVsUntyped() {
    // 타입 있는 상수
    const typedInt int = 100
    const typedFloat float64 = 3.14
    
    // 타입 없는 상수 (더 유연함)
    const untypedInt = 100
    const untypedFloat = 3.14
    
    // 타입 있는 상수는 정확한 타입 필요
    var i int = typedInt
    // var f float64 = typedInt // 컴파일 에러
    
    // 타입 없는 상수는 자동 변환
    var j int = untypedInt
    var k float64 = untypedInt  // OK: 자동 변환
    var l float32 = untypedFloat // OK: 자동 변환
    
    fmt.Println(i, j, k, l)
}

iota를 사용한 열거형

1. 기본 iota

const (
    Sunday = iota    // 0
    Monday           // 1
    Tuesday          // 2
    Wednesday        // 3
    Thursday         // 4
    Friday           // 5
    Saturday         // 6
)

func main() {
    fmt.Println(Sunday, Monday, Saturday)
    // 출력: 0 1 6
}

2. iota 시작 값 조정

const (
    January = iota + 1  // 1
    February            // 2
    March               // 3
    April               // 4
    May                 // 5
    June                // 6
    July                // 7
    August              // 8
    September           // 9
    October             // 10
    November            // 11
    December            // 12
)

3. iota 건너뛰기

const (
    _ = iota          // 0 무시
    KB = 1 << (10 * iota)  // 1 << 10 = 1024
    MB                     // 1 << 20 = 1048576
    GB                     // 1 << 30 = 1073741824
    TB                     // 1 << 40 = 1099511627776
)

const (
    Active = iota + 1  // 1
    _                  // 2 건너뛰기
    Inactive           // 3
    Deleted            // 4
)

4. 비트 플래그

const (
    FlagNone   = 0
    FlagRead   = 1 << iota  // 1 << 1 = 2
    FlagWrite              // 1 << 2 = 4
    FlagExecute            // 1 << 3 = 8
)

func main() {
    // 비트 연산으로 플래그 조합
    permissions := FlagRead | FlagWrite
    fmt.Printf("Permissions: %b\n", permissions) // 110 (binary)
    
    // 플래그 확인
    hasRead := permissions&FlagRead != 0
    hasExecute := permissions&FlagExecute != 0
    fmt.Println("Read:", hasRead)       // true
    fmt.Println("Execute:", hasExecute) // false
}

5. 복잡한 iota 표현식

const (
    B  = 1 << (10 * iota)  // 1
    KiB                     // 1024
    MiB                     // 1048576
    GiB                     // 1073741824
    TiB                     // 1099511627776
)

const (
    _           = iota             // 0
    First       = iota * 10 + 1    // 11
    Second                         // 21
    Third                          // 31
)

const (
    Hundred  = iota * 100  // 0
    TwoH                   // 100
    ThreeH                 // 200
    FourH                  // 300
)

6. 여러 상수에 같은 iota 적용

const (
    a, b = iota, iota * 10  // 0, 0
    c, d                     // 1, 10
    e, f                     // 2, 20
)

func main() {
    fmt.Println(a, b, c, d, e, f)
    // 출력: 0 0 1 10 2 20
}

상수 표현식

const (
    // 산술 연산
    sum  = 10 + 20           // 30
    diff = 100 - 50          // 50
    prod = 5 * 6             // 30
    quot = 100 / 4           // 25
    
    // 비트 연산
    bitAnd = 0b1100 & 0b1010 // 0b1000 = 8
    bitOr  = 0b1100 | 0b1010 // 0b1110 = 14
    bitXor = 0b1100 ^ 0b1010 // 0b0110 = 6
    
    // 시프트 연산
    leftShift  = 1 << 3      // 8
    rightShift = 16 >> 2     // 4
    
    // 문자열 연산
    greeting = "Hello, " + "World!"
    
    // 불리언 연산
    result = true && false   // false
    
    // 복합 표현식
    complex = (10 + 20) * 3 / 2  // 45
)

func main() {
    fmt.Println(sum, bitAnd, greeting, complex)
}

타입 있는 상수의 활용

type Status int

const (
    StatusPending Status = iota
    StatusRunning
    StatusCompleted
    StatusFailed
)

func (s Status) String() string {
    return [...]string{"Pending", "Running", "Completed", "Failed"}[s]
}

type FileMode uint32

const (
    ModeDir        FileMode = 1 << iota // 1
    ModeAppend                           // 2
    ModeExclusive                        // 4
    ModeTemporary                        // 8
    ModeSymlink                          // 16
)

func main() {
    status := StatusRunning
    fmt.Println(status)  // Running
    
    mode := ModeDir | ModeTemporary
    fmt.Printf("Mode: %b\n", mode)  // 1001
}

숫자 상수의 정밀도

func constantPrecision() {
    // 상수는 임의 정밀도 지원
    const huge = 1e1000
    const tiny = 1e-1000
    
    // 변수에 할당 시 타입 제한 적용
    // var x = huge // 오버플로우 에러
    
    // 표현식에서는 높은 정밀도 유지
    const result = huge / huge  // 1
    fmt.Println(result)
    
    // 부동소수점 상수
    const pi = 3.14159265358979323846264338327950288419716939937510
    fmt.Printf("%.50f\n", pi)
    
    // 정수 상수
    const maxInt64 = 9223372036854775807
    const beyondInt64 = maxInt64 * 2
    
    // 타입 없는 상수는 컨텍스트에 맞게 변환
    var i int = 100.0      // OK
    var f float64 = 100    // OK
}

실전 활용 패턴

1. HTTP 상태 코드

const (
    // 2xx Success
    StatusOK                   = 200
    StatusCreated              = 201
    StatusAccepted             = 202
    StatusNoContent            = 204
    
    // 3xx Redirection
    StatusMovedPermanently     = 301
    StatusFound                = 302
    StatusNotModified          = 304
    
    // 4xx Client Error
    StatusBadRequest           = 400
    StatusUnauthorized         = 401
    StatusForbidden            = 403
    StatusNotFound             = 404
    StatusMethodNotAllowed     = 405
    
    // 5xx Server Error
    StatusInternalServerError  = 500
    StatusNotImplemented       = 501
    StatusBadGateway           = 502
    StatusServiceUnavailable   = 503
)

func handleResponse(statusCode int) {
    switch statusCode {
    case StatusOK:
        fmt.Println("Success")
    case StatusNotFound:
        fmt.Println("Not Found")
    case StatusInternalServerError:
        fmt.Println("Server Error")
    default:
        fmt.Println("Unknown Status")
    }
}

2. 애플리케이션 설정

const (
    AppName        = "MyApplication"
    AppVersion     = "1.0.0"
    DefaultPort    = 8080
    DefaultTimeout = 30
    MaxRetries     = 3
    
    // 환경별 설정
    DevEnvironment  = "development"
    ProdEnvironment = "production"
)

type Config struct {
    Name    string
    Version string
    Port    int
}

func NewConfig() *Config {
    return &Config{
        Name:    AppName,
        Version: AppVersion,
        Port:    DefaultPort,
    }
}

3. 에러 메시지

const (
    ErrInvalidInput     = "invalid input provided"
    ErrNotFound         = "resource not found"
    ErrUnauthorized     = "unauthorized access"
    ErrInternalServer   = "internal server error"
    ErrTimeout          = "operation timeout"
)

func validateUser(id int) error {
    if id <= 0 {
        return errors.New(ErrInvalidInput)
    }
    // 검증 로직...
    return nil
}

4. 파일 권한 (Unix 스타일)

const (
    // 소유자 권한
    OwnerRead    = 0400
    OwnerWrite   = 0200
    OwnerExecute = 0100
    
    // 그룹 권한
    GroupRead    = 0040
    GroupWrite   = 0020
    GroupExecute = 0010
    
    // 기타 사용자 권한
    OtherRead    = 0004
    OtherWrite   = 0002
    OtherExecute = 0001
    
    // 일반적인 조합
    PermReadWrite     = OwnerRead | OwnerWrite | GroupRead | OtherRead // 0644
    PermExecutable    = PermReadWrite | OwnerExecute                    // 0744
    PermFullAccess    = 0777
)

func createFile(path string, perm int) {
    fmt.Printf("Creating file %s with permission %o\n", path, perm)
}

func main() {
    createFile("data.txt", PermReadWrite)
    createFile("script.sh", PermExecutable)
}

5. 로그 레벨

type LogLevel int

const (
    LogLevelDebug LogLevel = iota
    LogLevelInfo
    LogLevelWarn
    LogLevelError
    LogLevelFatal
)

func (l LogLevel) String() string {
    return [...]string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}[l]
}

type Logger struct {
    level LogLevel
}

func (l *Logger) log(level LogLevel, message string) {
    if level >= l.level {
        fmt.Printf("[%s] %s\n", level, message)
    }
}

func (l *Logger) Debug(msg string) { l.log(LogLevelDebug, msg) }
func (l *Logger) Info(msg string)  { l.log(LogLevelInfo, msg) }
func (l *Logger) Warn(msg string)  { l.log(LogLevelWarn, msg) }
func (l *Logger) Error(msg string) { l.log(LogLevelError, msg) }

func main() {
    logger := &Logger{level: LogLevelInfo}
    
    logger.Debug("This won't print")
    logger.Info("Application started")
    logger.Warn("High memory usage")
    logger.Error("Failed to connect")
}

6. 상태 머신

type State int

const (
    StateIdle State = iota
    StateInitializing
    StateRunning
    StatePaused
    StateStopped
    StateError
)

type Machine struct {
    state State
}

func (m *Machine) Start() error {
    if m.state != StateIdle {
        return fmt.Errorf("cannot start from state %d", m.state)
    }
    m.state = StateRunning
    return nil
}

func (m *Machine) Pause() error {
    if m.state != StateRunning {
        return fmt.Errorf("cannot pause from state %d", m.state)
    }
    m.state = StatePaused
    return nil
}

func (m *Machine) Stop() {
    m.state = StateStopped
}

7. 비트 마스크 패턴

type Permission uint8

const (
    PermNone Permission = 0
    PermRead Permission = 1 << iota
    PermWrite
    PermDelete
    PermShare
    PermAdmin
)

func (p Permission) Has(perm Permission) bool {
    return p&perm != 0
}

func (p Permission) Add(perm Permission) Permission {
    return p | perm
}

func (p Permission) Remove(perm Permission) Permission {
    return p &^ perm
}

func main() {
    // 권한 조합
    userPerm := PermRead | PermWrite
    adminPerm := PermRead | PermWrite | PermDelete | PermAdmin
    
    fmt.Println("User has Read:", userPerm.Has(PermRead))     // true
    fmt.Println("User has Admin:", userPerm.Has(PermAdmin))   // false
    
    // 권한 추가
    userPerm = userPerm.Add(PermShare)
    fmt.Println("User has Share:", userPerm.Has(PermShare))   // true
    
    // 권한 제거
    userPerm = userPerm.Remove(PermWrite)
    fmt.Println("User has Write:", userPerm.Has(PermWrite))   // false
}

변수 vs 상수 비교

func varVsConst() {
    // 변수: 런타임 값, 변경 가능
    var variable = time.Now().Unix()
    variable = 123
    
    // 상수: 컴파일 타임 값, 변경 불가
    const constant = 100
    // constant = 200 // 컴파일 에러
    
    // ❌ 런타임 함수 호출 결과는 상수 불가
    // const now = time.Now() // 컴파일 에러
    
    // ❌ 변수 값으로 상수 초기화 불가
    // const x = variable // 컴파일 에러
    
    // ✅ 상수 표현식은 가능
    const x = 10 + 20
    const y = x * 2
    
    fmt.Println(variable, constant, x, y)
}

일반적인 실수와 주의사항

1. 상수 재할당 시도

func reassignmentError() {
    const pi = 3.14159
    
    // ❌ 상수는 재할당 불가
    // pi = 3.14 // 컴파일 에러: cannot assign to pi
    
    // ✅ 새로운 상수 선언은 가능 (섀도잉)
    {
        const pi = 3.14 // 다른 스코프의 새 상수
        fmt.Println(pi) // 3.14
    }
    fmt.Println(pi) // 3.14159
}

2. 런타임 값으로 초기화

func runtimeValueError() {
    // ❌ 컴파일 에러: 런타임 함수 호출 불가
    // const now = time.Now()
    // const random = rand.Intn(100)
    // const length = len([]int{1, 2, 3})
    
    // ✅ 변수 사용
    var now = time.Now()
    var random = rand.Intn(100)
    
    // ✅ 리터럴 길이는 상수 가능
    const arrayLen = len([3]int{1, 2, 3})
    const strLen = len("hello")
}

3. 타입 있는 상수의 제약

func typedConstError() {
    const typedInt int = 100
    const typedFloat float64 = 3.14
    
    // ❌ 타입이 다르면 에러
    // var f float64 = typedInt // 컴파일 에러
    
    // ✅ 명시적 변환 필요
    var f float64 = float64(typedInt)
    
    // ✅ 타입 없는 상수는 자동 변환
    const untypedInt = 100
    var g float64 = untypedInt  // OK
    
    fmt.Println(f, g)
}

4. iota 재설정

const (
    A = iota  // 0
    B         // 1
    C         // 2
)

// 새 const 블록에서 iota는 0부터 시작
const (
    D = iota  // 0 (재설정됨)
    E         // 1
    F         // 2
)

func main() {
    fmt.Println(A, B, C) // 0 1 2
    fmt.Println(D, E, F) // 0 1 2
}

5. iota 표현식 재사용

const (
    X = iota * 2  // 0
    Y             // 2 (이전 표현식 재사용)
    Z             // 4
)

const (
    P = iota + 10  // 10
    Q = iota * 2   // 2 (새 표현식)
    R              // 4 (Q의 표현식 재사용)
)

func main() {
    fmt.Println(X, Y, Z) // 0 2 4
    fmt.Println(P, Q, R) // 10 2 4
}

6. 상수 오버플로우

func overflowExample() {
    // 상수 표현식은 임의 정밀도 지원
    const huge = 1 << 100
    
    // ❌ 변수 할당 시 타입 제한
    // var x int = huge // 오버플로우 에러
    
    // ✅ 적절한 타입 사용 또는 표현식에서만 사용
    const y = huge / huge  // 1
    
    fmt.Println(y)
}

모범 사례

1. 명명 규칙

// ✅ 상수는 PascalCase 또는 UPPER_SNAKE_CASE
const MaxConnections = 100
const DEFAULT_TIMEOUT = 30

// ✅ 열거형은 타입 접두사
type Status int
const (
    StatusPending Status = iota
    StatusActive
    StatusInactive
)

// ✅ 플래그는 명확한 이름
const (
    FlagReadOnly  = 1 << iota
    FlagHidden
    FlagSystem
)

2. 상수 그룹화

// ✅ 관련된 상수는 함께 그룹화
const (
    // Database settings
    DBHost     = "localhost"
    DBPort     = 5432
    DBName     = "myapp"
    DBUser     = "admin"
    
    // Cache settings
    CacheHost  = "localhost"
    CachePort  = 6379
    CacheTTL   = 3600
)

3. 문서화

// HTTPMethod represents HTTP request methods
type HTTPMethod string

const (
    // GET retrieves a resource
    MethodGet HTTPMethod = "GET"
    
    // POST creates a resource
    MethodPost HTTPMethod = "POST"
    
    // PUT updates a resource
    MethodPut HTTPMethod = "PUT"
    
    // DELETE removes a resource
    MethodDelete HTTPMethod = "DELETE"
)

4. 타입 안전성

// ✅ 타입 정의로 안전성 확보
type Color int

const (
    Red Color = iota
    Green
    Blue
)

func SetColor(c Color) {
    // Color 타입만 받음
}

func main() {
    SetColor(Red)    // OK
    // SetColor(1)   // 컴파일 에러 (strict mode에서)
}

정리

  • 상수는 const 키워드로 선언하며 컴파일 타임에 값 결정
  • 선언 시 초기화 필수, 재할당 불가
  • 타입 있는 상수와 타입 없는 상수 (더 유연함)
  • iota로 자동 증가 열거형 생성
  • 비트 플래그, 상태 코드, 권한 등에 활용
  • 상수 표현식은 임의 정밀도 지원
  • 런타임 함수 호출 결과는 상수 불가
  • const 블록마다 iota는 0부터 재시작
  • 명확한 명명과 그룹화로 가독성 향상
  • 타입 정의와 결합하여 타입 안전성 확보