Menu

Golang 타입 단언: x.(T), comma-ok, 타입 스위치

타입 단언은 인터페이스에서 구체적인 값을 꺼냅니다. x.(T), 패닉이 나지 않는 comma-ok 형태, 다른 인터페이스로 단언하기, 타입 스위치, 감싼 오류를 위한 errors.As를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

x.(T): 인터페이스에서 값 꺼내기

인터페이스 값은 자신이 담은 구체 타입을 숨깁니다. 타입 단언 x.(T)는 그것을 돌려 달라고 요청합니다.

출력:

gopher 6
0 false
string of length 6

x는 인터페이스 타입이어야 합니다. 구체 타입에 단언하면 컴파일에 실패합니다: invalid operation: s (variable of type string) is not an interface.

값 하나를 받는 형태는 패닉이 난다

단언이 틀리면 값 하나를 받는 형태는 두 타입을 모두 적은 메시지와 함께 패닉을 일으킵니다:

출력:

recovered: interface conversion: interface {} is int, not string

다른 타입이 들어오면 프로그래밍 오류로 보고 프로그램을 멈추고 싶을 때만 값 하나를 받는 형태를 쓰세요. 그 밖의 모든 경우에는 comma-ok를 씁니다. 실패하면 comma-ok는 T의 제로 값과 false를 줍니다.

nil 인터페이스에 대한 단언도 실패합니다. var v any; v.(string)interface conversion: interface {} is nil, not string으로 패닉이 나고, comma-ok 형태는 "", false를 반환합니다.

다른 인터페이스로 단언하기

T는 인터페이스 타입일 수도 있습니다. 이때 단언은 동적 값이 T를 구현하면 성공하고, 결과는 같은 동적 값을 유지합니다. 표준 라이브러리는 이 방식으로 선택적인 기능을 확인합니다.

io.Copy는 소스가 io.WriterTo를 구현하는지 확인하고, 구현한다면 그것을 씁니다. fmtStringererror를 확인합니다. 이 패턴 덕분에 작은 인터페이스는 작게 유지하면서도, 호출하는 쪽은 더 풍부한 구현의 이점을 누릴 수 있습니다.

타입 스위치

값이 여러 타입 중 하나일 수 있다면 타입 스위치가 단언의 연쇄를 대신합니다. x.(type)switch 안에서만 쓸 수 있습니다.

출력:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

알아 둘 규칙:

  • 타입이 하나인 case에서 x는 그 타입입니다. 여러 타입을 나열한 case와 default에서 x는 원래 인터페이스 타입(여기서는 any)입니다.
  • case는 순서대로 검사됩니다. 한 값이 여러 인터페이스를 만족할 수 있으므로, 더 구체적인 인터페이스를 넓은 인터페이스보다 앞에 두세요.
  • case nil은 nil 포인터를 담은 인터페이스가 아니라 nil 인터페이스에만 일치합니다.
  • 타입 스위치에는 fallthrough가 없습니다.

errors.As: 감싼 오류를 위한 단언

오류는 흔히 맥락과 함께 감싸집니다: fmt.Errorf("load config: %w", err). 직접적인 타입 단언은 가장 바깥 오류만 보고 안에 있는 오류를 놓칩니다. errors.As는 체인을 따라갑니다.

출력:

type assertion finds it: false
errors.As finds it: open /no/such/file

오류에 타입 단언을 쓰는 것은 오류가 감싸지지 않았다고 확신할 때뿐이며, 실제로는 거의 없는 일입니다. errors.As, errors.Is와 직접 오류 타입을 정의하는 방법은 커스텀 오류 페이지에서 다룹니다.

단언, 변환, 제네릭

가진 것원하는 것사용할 것
intfloat64변환: float64(n)
int를 담은 anyint단언: v.(int)
여러 타입이 올 수 있는 any타입에 따라 분기타입 스위치
감싸졌을 수 있는 error특정 오류 타입errors.As
여러 타입에 동작하는 함수컴파일 타임 안전성제네릭

any와 타입 스위치로 가득한 코드는 제네릭이나 제대로 된 인터페이스가 의도를 더 잘 표현하고 실수를 컴파일 타임에 잡을 수 있다는 신호인 경우가 많습니다.

흔한 실수

  • 신뢰할 수 없는 데이터에 패닉이 나는 형태를 씀. 디코딩한 JSON, any 타입의 맵 값, 플러그인 입력에는 항상 comma-ok를 쓰세요.
  • 엉뚱한 숫자 타입으로 단언함. JSON 숫자는 any로 디코딩될 때 float64가 되므로 v.(int)는 실패합니다.
  • 포인터가 저장되어 있는데 값 타입으로 단언함. 인터페이스가 *User를 담고 있다면 v.(User)는 실패합니다. v.(*User)로 단언하세요.
  • 오류에 타입 단언을 씀. errors.As를 쓰세요.

자주 묻는 질문

Go에서 타입 단언이란 무엇인가요?

x가 인터페이스 값일 때 쓰는 x.(T) 표현식입니다. T가 구체 타입이면 x에 저장된 값을 T로 반환합니다. T가 인터페이스 타입이면 저장된 값이 T도 구현하는지 확인합니다. 값 하나를 받는 형태는 확인에 실패하면 패닉이 나고, 값 둘을 받는 형태 v, ok := x.(T)는 실패를 ok로 알려 줍니다.

Go에서 인터페이스 값의 타입은 어떻게 확인하나요?

타입 스위치를 씁니다: switch v := x.(type) { case int: ...; case string: ...; default: ... }. 각 case 안에서 v는 그 case의 타입을 가집니다. 타입 하나만 확인한다면 comma-ok 단언 s, ok := x.(string)이 더 짧습니다. 디버깅용으로는 fmt.Printf("%T", x)가 타입 이름을 출력합니다.

Go에서 타입 단언과 타입 변환은 무엇이 다른가요?

변환 T(x)float64(n)처럼 한 타입의 값을 다른 타입으로 바꿉니다. 허용 여부는 컴파일러가 정하고 런타임에 실패하지 않습니다. 단언 x.(T)는 인터페이스 값에만 쓸 수 있고, 안에 저장된 타입을 런타임에 확인합니다. int를 담은 anyint(x)라고 쓸 수는 없고, x.(int)가 필요합니다.

오류 타입을 확인할 때 타입 단언을 써야 하나요?

아니요. errors.As(err, &target)를 쓰세요. 직접 단언하는 err.(*MyError)는 바깥 오류만 보므로, 오류가 fmt.Errorf("...: %w", err)로 감싸지는 순간 실패합니다. errors.As는 감싼 체인을 따라갑니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기