
Go 제네릭, 그 다음 단계 이야기
Go 언어를 써본 분이라면 2022년에 추가된 제네릭(Generics) 기능을 기억하실 거예요. 제네릭이 뭐냐면, 똑같은 로직인데 타입만 다른 함수를 매번 새로 만들지 않고 "이 함수는 어떤 타입이든 받을 수 있어"라고 한 번만 정의해두는 기능이에요. 예를 들어 정수 리스트에서 최댓값을 찾는 함수와 문자열 리스트에서 최댓값을 찾는 함수를 따로 만들지 않고, Max[T] 하나로 끝낼 수 있는 거죠.
그런데 Go의 제네릭에는 묘한 제약이 하나 있었어요. 함수에는 타입 파라미터를 붙일 수 있는데, 메서드에는 못 붙인다는 점이에요. 즉 func MapT, U any U) []U 같은 일반 함수는 되는데, 어떤 구조체에 func (s Stream[T]) MapU any U) Stream[U] 이런 식으로 메서드에 새 타입 파라미터를 추가하는 건 막혀 있었어요. 이번에 다시 불붙은 GitHub 이슈 #77273은 바로 이 제약을 풀자는 제안이에요.
왜 지금까지 막아뒀나
사실 이 문제는 제네릭이 처음 들어올 때부터 알려져 있었어요. Go 팀이 일부러 빼둔 거였거든요. 이유는 두 가지였어요. 첫째, 인터페이스와의 충돌 문제예요. Go의 인터페이스는 "이런 메서드를 가진 타입이면 다 받아줄게"라는 식으로 동작하는데, 메서드 자체에 타입 파라미터가 붙어버리면 "이 메서드를 가졌다"를 판단하는 게 갑자기 어려워져요. 예를 들어 Map[U any] 메서드를 가진 인터페이스가 있다면, 그걸 만족하려면 가능한 모든 U에 대해 메서드가 존재해야 하는데, 이걸 컴파일 시점에 어떻게 확인할 거냐는 문제가 생기는 거죠.
둘째는 구현 복잡도예요. Go는 컴파일 속도와 단순함을 자랑으로 삼는 언어인데, 제네릭 메서드를 제대로 지원하려면 런타임에 타입 정보를 더 많이 들고 다녀야 하거나, 컴파일 시 더 많은 코드를 생성해야 해요. C#이나 Rust처럼 단형화(monomorphization, 타입별로 함수를 따로 컴파일하는 방식)를 쓰면 바이너리 크기가 커지고, Java처럼 타입 소거(erasure)를 쓰면 성능이 떨어져요. Go는 이 둘을 섞은 "GC 모양 기반 스텐실링"이라는 독특한 방식을 쓰는데, 메서드까지 확장하면 더 복잡해지는 거예요.
그럼에도 다시 꺼낸 이유
Go 커뮤니티에서 가장 답답해하는 게 바로 함수형 체이닝이에요. JavaScript에서 arr.map(...).filter(...).reduce(...) 이렇게 쭉 이어 쓰는 그 패턴이요. Go에서도 표준 라이브러리에 iter 패키지가 들어오면서 이터레이터 기반 처리가 가능해졌는데, 정작 Stream[T].Map[U] 같은 자연스러운 체이닝은 못 쓰는 상황이에요. 그래서 결국 slices.Map(slices.Filter(...)) 처럼 거꾸로 읽히는 코드를 짜거나, 매번 중간 변수를 만들어야 했죠.
이번 제안에서는 인터페이스에는 여전히 제네릭 메서드를 못 넣더라도, 구체 타입(concrete type)의 메서드에는 허용하자는 절충안이 논의되고 있어요. 이러면 인터페이스 만족 여부 판단 문제는 우회할 수 있거든요. 실제로 Rust도 트레이트 메서드에 제네릭을 허용하되 dyn(동적 디스패치) 시에는 제약을 두는 식으로 처리하고 있어요.
다른 언어와 비교해보면
C#은 제네릭 메서드를 자유롭게 쓸 수 있어요. List<T> 클래스 안에 ConvertAll<U>(Func<T,U> converter) 같은 메서드가 자연스럽게 있죠. Java도 마찬가지로 <U> List<U> map(Function<T,U> f) 식으로 쓸 수 있고요. TypeScript의 Array.prototype.map<U>도 같은 맥락이에요. Go만 이걸 못 한다는 게 "현대적인 정적 타입 언어"라는 자기 정체성과 살짝 어긋나는 부분이었어요.
반대로 Go의 단순함을 지키자는 쪽 의견도 만만치 않아요. "필요하면 일반 함수로 쓰면 되잖아"라는 거죠. 실제로 Go의 표준 라이브러리 slices, maps 패키지가 다 그 방식으로 만들어져 있고, 잘 동작하고 있어요.
한국 개발자 입장에서는
백엔드에서 Go를 쓰는 팀이라면 이 변화가 꽤 반가울 거예요. 특히 데이터 파이프라인이나 ETL 같이 변환을 여러 번 거치는 코드를 짤 때 코드 가독성이 확 올라가거든요. 다만 당장 프로덕션 코드에 영향을 줄 만한 변화는 아니에요. Go의 언어 변경은 제안서가 통과돼도 실제 릴리스까지 1~2년이 걸리는 게 보통이고, 이번 제안도 "논의 중" 단계니까요.
그래도 지금 시점에서 알아두면 좋은 건, Go가 점점 더 표현력 있는 언어로 진화하는 방향을 잡았다는 거예요. 초기 Go의 "단순함 절대주의"에서 한 발 물러나, 실용성을 위해 복잡도를 받아들이기 시작했다는 신호로 볼 수 있어요. 제네릭 도입 자체가 그 시작이었고, 이번 제네릭 메서드 논의가 그 흐름의 다음 챕터인 거죠.
마무리
핵심 한 줄로 정리하면, Go가 제네릭의 마지막 퍼즐 조각인 "메서드의 타입 파라미터" 지원을 두고 진지하게 논의를 시작했다는 거예요. 인터페이스 호환성과 컴파일 복잡도라는 두 벽을 어떻게 넘을지가 관건이고요.
여러분은 어떻게 생각하세요? Go가 점점 다른 정적 타입 언어들처럼 표현력을 갖춰가는 게 반갑나요, 아니면 "단순한 Go"의 매력이 사라지는 것 같아 아쉬우신가요?
🔗 출처: Hacker News
TTJ 코딩클래스 정규반
월급 외 수입,
코딩으로 만들 수 있습니다
17가지 수익 모델을 직접 실습하고, 1,300만원 상당의 자동화 도구와 소스코드를 받아가세요.
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공