Go 제네릭 1.18 출시, 실무 코드에 적용해보기
배경
어제 Go 1.18이 릴리스되면서 제네릭이 정식 지원되었다. 그동안 사내 API 서버에서 interface{}와 타입 단언을 남발하거나, 타입별로 동일한 로직의 함수를 중복 작성하는게 불편했는데, 이번 기회에 실제 코드에 적용해봤다.
기존 코드의 문제
슬라이스 필터링 함수를 int, string 등 타입별로 각각 만들어 사용하고 있었다.
func FilterInts(slice []int, fn func(int) bool) []int {
result := []int{}
for _, v := range slice {
if fn(v) {
result = append(result, v)
}
}
return result
}
func FilterStrings(slice []string, fn func(string) bool) []string {
// 동일한 로직 반복
}
제네릭 적용
이제 하나의 함수로 통합할 수 있다.
func Filter[T any](slice []T, fn func(T) bool) []T {
result := []T{}
for _, v := range slice {
if fn(v) {
result = append(result, v)
}
}
return result
}
// 사용
ids := Filter([]int{1, 2, 3, 4}, func(n int) bool { return n > 2 })
names := Filter([]string{"a", "b"}, func(s string) bool { return len(s) > 0 })
타입 제약 활용
comparable 제약으로 중복 제거 함수도 깔끔하게 작성할 수 있었다.
func Unique[T comparable](slice []T) []T {
seen := make(map[T]struct{})
result := []T{}
for _, v := range slice {
if _, ok := seen[v]; !ok {
seen[v] = struct{}{}
result = append(result, v)
}
}
return result
}
체감한 장단점
장점:
- 코드 중복이 확실히 줄었다
- 타입 안정성을 유지하면서 재사용성이 높아졌다
- 외부 라이브러리 의존도를 줄일 수 있었다
단점:
- 컴파일 시간이 체감상 10~15% 늘어난 느낌이다
- 에러 메시지가 복잡해져서 익숙해지는데 시간이 필요할 것 같다
- 아직 표준 라이브러리에 제네릭 적용이 부족해서 직접 구현해야 하는 부분이 많다
마무리
당장 프로덕션 코드에 전면 도입하기보다는, 유틸 함수나 컨테이너 타입부터 점진적으로 적용해볼 예정이다. 팀 내 Go 1.18 마이그레이션은 다음주에 진행하기로 했다.