인스턴스화
인스턴스화 (Instantiations)
제네릭 함수나 메서드, 타입을 실제로 '쓸 수 있는' 형태로 만들려면, 타입 매개변수 자리에 구체적인 타입 인자를 채워 넣어야 해요. 바로 이 과정을 인스턴스화(instantiation) 라고 부르는데요, Go 1.18과 Go 1.27에서 정의된 제네릭의 핵심 동작이에요. 이번 절에서는 그 인스턴스화가 정확히 어떤 두 단계로 진행되는지, 그리고 타입 인자를 언제 생략할 수 있는지를 하나씩 살펴볼게요.
출처: Go Specification
본문
제네릭 함수, 메서드, 타입은 타입 매개변수 자리에 타입 인자(type argument)를 대입하면서 인스턴스화됩니다 (Go 1.18 / Go 1.27). 인스턴스화는 두 단계로 진행돼요.
- 각 타입 인자를 제네릭 선언 안의 해당 타입 매개변수 자리에 대입합니다. 이 대입은 함수나 타입 선언 전체에 걸쳐 일어나는데, 타입 매개변수 목록 자체와 그 목록 안의 모든 타입도 포함돼요.
- 대입이 끝나면, 각 타입 인자는 해당 타입 매개변수의 제약(constraint)(필요하면 인스턴스화된)을 만족(satisfy)해야 합니다. 그렇지 않으면 인스턴스화는 실패해요.
제네릭 타입, 함수, 메서드를 인스턴스화하면 각각 제네릭이 아닌(non-generic) 타입, 함수, 메서드가 됩니다.
type parameter list type arguments after substitution
[P any] int int satisfies any
[S ~[]E, E any] []int, int []int satisfies ~[]int, int satisfies any
[P io.Writer] string illegal: string doesn't satisfy io.Writer
[P comparable] any any satisfies (but does not implement) comparable
제네릭 함수나 메서드를 사용할 때는 타입 인자를 명시적으로 줄 수도 있고, 함수가 쓰이는 문맥에서 타입 인자를 부분적으로 또는 완전히 추론(infer)할 수도 있어요. 타입 인자를 추론할 수 있다면, 다음 경우에는 타입 인자 목록을 아예 생략할 수 있죠.
- 보통의 인자로 호출(call)될 때
- 타입이 알려진 변수에 할당(assign)될 때
- 다른 함수의 인자로 전달될 때
- 결과로 반환(return)될 때
그 밖의 모든 경우에는 (부분적일 수 있는) 타입 인자 목록이 반드시 있어야 해요. 타입 인자 목록이 아예 없거나 부분적이라면, 빠진 모든 타입 인자는 함수가 쓰이는 문맥에서 추론할 수 있어야 합니다.
// sum returns the sum (concatenation, for strings) of its arguments.
func sum[T ~int | ~float64 | ~string](x... T) T { … }
x := sum // illegal: the type of x is unknown
intSum := sum[int] // intSum has type func(x... int) int
a := intSum(2, 3) // a has value 5 of type int
b := sum[float64](2.0, 3) // b has value 5.0 of type float64
c := sum(b, -1) // c has value 4.0 of type float64
type sumFunc func(x... string) string
var f sumFunc = sum // same as var f sumFunc = sum[string]
f = sum // same as f = sum[string]
부분 타입 인자 목록은 비어 있을 수 없어요. 적어도 첫 번째 인자는 반드시 있어야 합니다. 그 목록은 전체 타입 인자 목록의 접두사(prefix) 역할을 해서, 나머지 인자들은 추론에 맡겨요. 대략적으로 말하면 타입 인자는 "오른쪽에서 왼쪽으로" 생략할 수 있다고 볼 수 있죠.
func apply[S ~[]E, E any](s S, f func(E) E) S { … }
f0 := apply[] // illegal: type argument list cannot be empty
f1 := apply[[]int] // type argument for S explicitly provided, type argument for E inferred
f2 := apply[[]string, string] // both type arguments explicitly provided
var bytes []byte
r := apply(bytes, func(byte) byte { … }) // both type arguments inferred from the function arguments
제네릭 타입의 경우에는 타입 인자를 항상 전부 명시적으로 제공해야 합니다.
더 알아보기
- Type inference — 타입 인자를 문맥에서 추론하는 규칙. 인스턴스화와 짝을 이루는 절이에요.
- Type parameter declarations — 타입 매개변수와 제약을 선언하는 문법.
- Satisfying a type constraint — 타입 인자가 제약을 만족하는지 판정하는 규칙.
- Calls — 제네릭 함수를 인자 가진 채로 호출하면서 타입 인자를 생략하는 경우.