이 글은 Effective Go를 일부 번역한 글입니다.


초기화

C나 C++과는 겉보기에 크게 다르지 않음에도 Go의 초기화는 더 강력하다. 초기화 중에 복잡한 구조체를 구성할 수 있고 객체나 서로 다른 패키지를 초기화하는 순서 문제도 바르게 다뤄진다.

상수

Go의 상수는 말 그대로 상수다. 상수는 컴파일 시간에 생성되며, 심지어 함수 안에 정의해도 그렇다. 또한 오직 숫자, 문자(룬), 문자열, 논리 자료형만 가능하다. 컴파일 시간 제약 때문에 상수를 정의하는 식은 반드시 상수 식이어야 하고 컴파일러가 계산할 수 있어야 한다. 예를 들어 1<<3 은 상수 식이지만, math.Sin(math.Pi/4)math.Sin 함수를 실행 시간에 호출해야 하기 때문에 상수 식이 아니다.

Go는 열거형 상수를 iota 열거자로 만든다. iota를 식의 일부로 쓸 수 있고 식은 암묵적으로 반복될 수 있기 때문에 쉽게 복잡한 값 묶음을 만들 수 있다.

type ByteSize float64

const (
    _           = iota // ignore first value by assigning to blank identifier
    KB ByteSize = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)
String 같은 메서드를 사용자 정의 유형에 붙일 수 있기 때문에 임의의 값의 출력 형태를 스스로 정할 수 있다. 이 기법은 흔히 구조체에 적용하지만, 스칼라 유형, 예를 들면 ByteSize 같은 실수 유형에도 유용하다.

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)
}

YB1.00YB로 출력되고, ByteSize(1e13)9.09TB로 출력된다.

ByteSizeString 메서드를 구현하는 데 쓴 Sprintf는 안전하다 (무한 재귀 호출을 하지 않는다). 변환 문제가 아니라 Sprintf 호출에 %f를 썼기 때문이다. %f는 문자열 형식이 아니며 Sprintf는 문자열이 필요할 때만 String을 호출한다. %f는 실수 값이 필요하다.

변수

변수는 상수처럼 초기화할 수 있지만 실행 시간에 계산하는 일반 식으로 초기화할 수 있다.

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)