타입 동일성
타입 동일성
두 타입이 같은 타입인지 판별해야 하는 순간이 있어요. 예컨대 함수에 값을 넘길 때, 구조체 필드끼리 대입할 때, 그리고 제네릭 코드에서 타입 인자가 맞는지 확인할 때가 그렇죠. Go는 이 '같은 타입'을 어떻게 가르는지 아주 엄격하게 규정해 두었어요. 오늘은 그 기준을 하나씩 살펴볼게요.
출처: Go Specification
본문
요점부터 말하면, 두 타입은 동일(identical) 하거나("같다") 서로 다르다(different) 둘 중 하나예요. 중간이 없어요.
여기서 핵심 규칙이 하나 나옵니다. 이름 붙은 타입(named type) 은 항상 다른 어떤 타입과도 다르다고 봐요. 반면 그 외의 경우에는, 두 타입의 밑바탕(underlying) 타입 리터럴이 구조적으로 동등하면 동일한 타입이에요. 즉 같은 리터럴 구조를 갖고, 대응하는 구성 요소가 동일한 타입을 가질 때죠. 구체적으로는 이렇게 판정해요.
- 두 배열(Array) 타입은 요소 타입이 동일하고 배열 길이도 같을 때 동일해요.
- 두 슬라이스(Slice) 타입은 요소 타입이 동일할 때 동일해요.
- 두 구조체(Struct) 타입은 필드의 순서가 같고, 대응하는 필드 쌍이 이름·타입·태그(tag)가 모두 같고, 둘 다 임베디드(embedded)이거나 둘 다 임베디드가 아닐 때 동일해요. 단, 서로 다른 패키지에서 온 비-exported 필드 이름은 항상 서로 다르다고 봐요.
- 두 포인터(Pointer) 타입은 밑 타입(base type)이 동일할 때 동일해요.
- 두 함수(Function) 타입은 파라미터와 결과 값의 개수가 같고, 대응하는 파라미터·결과 타입이 동일하며, 둘 다 가변 인자(variadic)이거나 둘 다 가변 인자가 아닐 때 동일해요. 파라미터와 결과의 이름은 일치할 필요가 없어요.
- 두 인터페이스(Interface) 타입은 같은 타입 집합(type set)을 정의할 때 동일해요.
- 두 맵(Map) 타입은 키 타입과 요소 타입이 동일할 때 동일해요.
- 두 채널(Channel) 타입은 요소 타입이 동일하고 방향도 같을 때 동일해요.
- 두 인스턴스화(instantiated) 타입은 정의된 타입과 모든 타입 인자가 동일할 때 동일해요.
여기서 헷갈리기 쉬운 포인트를 하나 짚을게요. 이름 붙은 타입은 항상 다른 타입과 다르다는 규칙 덕분에, 같은 밑바탕 구조를 공유하더라도 이름이 다르면 서로 다른 타입으로 취급돼요. 이게 '새 타입을 만들었다'는 바로 그 의미예요. 반대로 =으로 만든 타입 별칭(alias) 은 별칭일 뿐이라 동일한 타입으로 취급되고요.
다음 선언들을 볼게요.
type (
A0 = []string
A1 = A0
A2 = struct{ a, b int }
A3 = int
A4 = func(A3, float64) *A0
A5 = func(x int, _ float64) *[]string
B0 A0
B1 []string
B2 struct{ a, b int }
B3 struct{ a, c int }
B4 func(int, float64) *B0
B5 func(x int, y float64) *A1
C0 = B0
D0[P1, P2 any] struct{ x P1; y P2 }
E0 = D0[int, string]
)
이 선언 아래에서 다음 타입들은 서로 동일해요.
A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5
B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5
동일함을 보여주는 예시와 함께, 왜 다르다고 판정되는지도 곁들여서 볼게요.
B0과B1은 서로 다른 타입이에요. 둘 다 서로 다른 타입 정의로 만들어진 새 타입이기 때문이죠.func(int, float64) *B0와func(x int, y float64) *[]string은 서로 다른 타입이에요.B0이[]string과 다르기 때문이에요.P1과P2는 서로 다른 타입 파라미터라서 서로 다른 타입이에요.D0[int, string]과struct{ x int; y string }은 서로 다른 타입이에요. 전자는 인스턴스화된 정의 타입인 반면, 후자는 타입 리터럴이거든요. 다만 둘은 여전히 서로 대입 가능(assignable)해요.
여기까지가 '타입 동일성'의 전부예요. 기준은 결국 이거예요. 구조가 서로 같고, 또 이름 붙은 타입끼리는 같은 정의에서 나왔는지를 따지는 것. 그래서 실제로 타입을 비교해야 할 때는 이 규칙을 머릿속에 두고 코드를 보시면 어렵지 않게 판별할 수 있어요.
더 알아보기
- Go Specification - Type identity: 이 글의 원문이에요. 더 자세한 설명과 최신 내용을 확인하고 싶다면 여기로.
- Type definitions: 타입 정의로 새 타입을 만드는 규칙이에요. '이름 붙은 타입은 항상 다르다'는 원칙과 연결돼요.
- Assignability: 같은 타입이 아니어도 어떤 경우에 서로 대입 가능한지를 다뤄요.