타입 단언(Type assertions)

타입 단언(Type assertions)

인터페이스 값에 담긴 실제 타입을 되찾아올 때 쓰는 표현이 있어요. 바로 x.(T) 형태의 **타입 단언(type assertion)**이죠. 컴파일 시점에는 인터페이스의 동적 타입이 뭔지 알 수 없으니, "지금 이 값은 정말 T 타입이 맞는지"를 프로그램이 확인하도록 하는 거예요. 조건이 맞으면 값을 그대로 꺼내고, 아니면 프로그램이 크게 놀라며(런타임 패닉) 알려줘요.

출처: Go Specification

본문

인터페이스 타입(Interface types)을 가진 표현식 x — 단, 타입 매개변수(type parameter)는 제외 — 와 타입 T에 대해, 기본 표현식(primary expression)은

x.(T)

xnil이 아니고 x에 저장된 값이 T 타입임을 단언(assert)해요. 이 x.(T) 표기법을 타입 단언이라고 불러요.

좀 더 정확히 말하면, T가 인터페이스 타입이 아닐 때 x.(T)x의 동적 타입이 T 타입과 동일(identical)함을 단언해요. 이 경우 T는 반드시 x의 (인터페이스) 타입을 구현(implement)해야 해요. 그렇지 않으면 xT 타입의 값을 저장할 수 없으므로 타입 단언 자체가 성립하지 않죠. 반대로 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로 성공 여부를 먼저 확인할 수 있기 때문이죠. 인터페이스와 동적 타입의 개념, 그리고 타입이 어떻게 결정되는지는 링크의 다른 섹션과 함께 보면 더 자연스럽게 연결돼요.