Go 1.18 베타 제네릭 도입 후기

배경

사내 백엔드 API 서버에서 반복적인 슬라이스 처리 로직이 많았다. interface{}를 남발하거나 같은 로직을 타입별로 복사-붙여넣기하는 상황이 지속되어 제네릭 도입을 검토했다.

Go 1.18 베타가 공개되어 로컬 환경에서 테스트해봤다.

적용 사례

가장 먼저 슬라이스 필터 함수를 제네릭으로 작성했다.

func Filter[T any](slice []T, fn func(T) bool) []T {
    result := make([]T, 0)
    for _, item := range slice {
        if fn(item) {
            result = append(result, item)
        }
    }
    return result
}

// 사용
users := []User{{ID: 1, Active: true}, {ID: 2, Active: false}}
activeUsers := Filter(users, func(u User) bool {
    return u.Active
})

기존에는 타입별로 FilterUsers, FilterProducts 같은 함수를 각각 만들었는데, 이제 하나로 통일할 수 있게 됐다.

제약 조건 활용

숫자 연산이 필요한 경우 제약 조건을 정의했다.

type Number interface {
    int | int64 | float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

마주친 문제

  1. 컴파일 시간 증가: 제네릭 함수가 많아지자 빌드 시간이 약 20% 늘었다.
  2. 에러 메시지 복잡도: 타입 추론 실패 시 에러 메시지가 길고 이해하기 어려웠다.
  3. 기존 코드와 혼용: interface{}를 사용하던 레거시 코드와 제네릭 코드를 함께 사용하니 타입 변환 지점에서 혼란이 생겼다.

결론

제네릭은 확실히 코드 중복을 줄이고 타입 안정성을 높였다. 하지만 아직 베타 단계라 프로덕션 적용은 보류했다. 정식 릴리스 후 점진적으로 도입할 계획이다.

당장은 유틸리티 함수 레이어부터 적용하고, 핵심 비즈니스 로직은 안정화 이후 전환하려고 한다.

Go 1.18 베타 제네릭 도입 후기