Go 1.18 베타 제네릭 도입 후기

배경

사내 백엔드 프로젝트에서 슬라이스 조작 함수들이 타입별로 중복되어 있었다. FilterInt, FilterString, MapInt 같은 식으로 5~6개 타입마다 동일한 로직이 반복되고 있었다.

Go 1.18 베타가 12월 초 공개되면서 제네릭이 추가되었다는 소식에 로컬 환경에 설치해서 테스트해봤다.

기존 코드

func FilterInt(slice []int, fn func(int) bool) []int {
    result := []int{}
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

func FilterString(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
}

// 사용
filtered := Filter([]int{1, 2, 3, 4}, func(n int) bool {
    return n > 2
})

타입 파라미터 문법이 대괄호 [T any]를 사용한다. 처음엔 낯설었지만 금방 익숙해졌다.

제약 조건

숫자 타입만 받는 함수가 필요한 경우 constraints 패키지를 사용할 수 있다.

import "golang.org/x/exp/constraints"

func Sum[T constraints.Ordered](slice []T) T {
    var sum T
    for _, v := range slice {
        sum += v
    }
    return sum
}

느낀 점

  • 코드 중복이 확실히 줄어들었다
  • 컴파일 타임에 타입 안정성 확보
  • 문법이 생각보다 직관적이다
  • 아직 베타라 프로덕션 적용은 내년 정식 릴리즈 이후에 고려 예정

기존 Go 철학과 맞지 않는다는 의견도 있지만, 실용적인 측면에서 환영할만한 변화였다.