타입 단언(Type assertions)
타입 단언(Type assertions)
인터페이스 값에 담긴 실제 타입을 되찾아올 때 쓰는 표현이 있어요. 바로 x.(T) 형태의 **타입 단언(type assertion)**이죠. 컴파일 시점에는 인터페이스의 동적 타입이 뭔지 알 수 없으니, "지금 이 값은 정말 T 타입이 맞는지"를 프로그램이 확인하도록 하는 거예요. 조건이 맞으면 값을 그대로 꺼내고, 아니면 프로그램이 크게 놀라며(런타임 패닉) 알려줘요.
출처: Go Specification
본문
인터페이스 타입(Interface types)을 가진 표현식 x — 단, 타입 매개변수(type parameter)는 제외 — 와 타입 T에 대해, 기본 표현식(primary expression)은
x.(T)
x가 nil이 아니고 x에 저장된 값이 T 타입임을 단언(assert)해요. 이 x.(T) 표기법을 타입 단언이라고 불러요.
좀 더 정확히 말하면, T가 인터페이스 타입이 아닐 때 x.(T)는 x의 동적 타입이 T 타입과 동일(identical)함을 단언해요. 이 경우 T는 반드시 x의 (인터페이스) 타입을 구현(implement)해야 해요. 그렇지 않으면 x에 T 타입의 값을 저장할 수 없으므로 타입 단언 자체가 성립하지 않죠. 반대로 T가 인터페이스 타입이면, x.(T)는 x의 동적 타입이 인터페이스 T를 구현함을 단언해요.
타입 단언이 성립하면 표현식의 값은 x에 저장된 값이고 그 타입은 T가 돼요. 만약 타입 단언이 거짓이면 런타임 패닉(run-time panic)이 발생해요. 다시 말해, x의 동적 타입은 런타임에만 알 수 있지만, 올바른 프로그램에서는 x.(T)의 타입이 T라는 사실은 컴파일 시점에 이미 알 수 있어요.
var x interface{} = 7 // x has dynamic type int and value 7
i := x.(int) // i has type int and value 7
type I interface { m() }
func f(y I) {
s := y.(string) // illegal: string does not implement I (missing method m)
r := y.(io.Reader) // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
…
}
타입 단언은 대입문(assignment statement)이나 특수한 형태의 초기화에서 쓰이면
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // dynamic types of v and ok are T and bool
추가로 타입이 지정되지 않은(untyped) 불리언 값을 하나 더 내놓아요. 단언이 성립하면 ok의 값은 true이고, 아니면 false이고 v의 값은 T 타입의 제로 값(zero value)이 돼요. 이 경우에는 런타임 패닉이 발생하지 않아요.
더 알아보기
타입 단언을 자주 쓸 때마다 패닉 위험을 안게 되니, 값이 정말 그 타입인지 불확실한 상황이라면 v, ok := x.(T) 형태의 "콤마-ok" 관용구를 쓰는 게 안전해요. ok로 성공 여부를 먼저 확인할 수 있기 때문이죠. 인터페이스와 동적 타입의 개념, 그리고 타입이 어떻게 결정되는지는 링크의 다른 섹션과 함께 보면 더 자연스럽게 연결돼요.