제네릭 또는 generics는 Go 1.18부터 도입되었습니다. 오랜 시간 동안 Go는 단순함을 위한 보수적인 언어로 여겨졌고, 제네릭은 제외되었지만 프로젝트 수의 증가와 생태계의 발전으로 범용 함수와 구조체 작성을 필요로 하게 되었습니다. 이는 특히 컨테이너 구조체, 컬렉션 처리 알고리즘, 인프라 코드를 위해 매우 중요합니다.
문제: 제네릭이 도입되기 전에는 코드를 중복 작성해야 하거나 빈 인터페이스 (interface{})를 사용하여 타입 안전성을 잃고 성능 저하와 디버깅 복잡화가 있었습니다.
해결책: Go에서 제네릭은 함수와 타입에 대괄호 안에 타입 매개변수로 구현되었습니다. 제약조건(constraints)을 통해 허용되는 타입 매개변수를 제한할 수 있습니다. 이는 타입 안전성을 잃지 않고 범용 함수를 작성할 수 있게 해줍니다.
코드 예시:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
주요 특징:
제네릭에서 모든 타입 매개변수 T에 대해 산술 연산(+, -, *, /)을 사용할 수 있나요?
아니요. Go 컴파일러는 타입 매개변수가 산술 연산을 지원하는지 알지 못합니다. 이 경우 제약조건을 명시해야 하며, 예를 들어 Go 1.18 이상부터 연산자 제약조건이 있는 인터페이스를 사용해야 합니다.
코드 예시:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
제네릭 컨테이너가 서로 다른 타입에 대해 다른 동작을 가진 메소드를 포함할 수 있나요?
아니요. 제네릭 구조체 또는 함수의 메소드는 모든 타입에 대해 동일해야 하며, 메소드 내에서 타입 스위치를 사용하지 않는 경우에만 가능합니다. 동작은 제약조건을 통해 정의되거나 순수하게 매개변수화되어야 합니다.
패키지 수준에서 타입 매개변수(제네릭 타입)를 만들 수 있나요?
예, Go 1.18부터 제네릭 함수와 제네릭 구조체 타입 모두를 만들 수 있습니다:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
컬렉션 작업을 위한 라이브러리 내에서 interface{}를 사용한 범용 Map 기능이 구현되었습니다:
장점:
단점:
같은 프로젝트에서 제네릭으로 전환하고 인터페이스를 통해 제약조건을 설정했습니다:
장점:
단점: