Go는 오랫동안 내장 슬라이스와 맵의 유연함을 강조하며 표준 라이브러리에 컬렉션 자료구조를 거의 두지 않았다. 우선순위 큐에 쓰이는 힙 정도가 있을 뿐, 집합(set)은 관례적으로 map[T]bool이나 map[T]struct{}로 표현해 왔고, 이진 트리 기반의 정렬 맵이나 정렬 집합은 아예 없었다. 2025년 말 결성된 Go 컬렉션 워킹그룹은 이 공백을 메우기 위해 만들어졌다. Jonathan Amsterdam, Alan Donovan, Robert Griesemer, Daniel Martí, Roger Peppe, Keith Randall, Ian Lance Taylor 등 언어 설계에 깊이 관여해 온 인물들이 참여했고, 이번에 Go 1.28을 겨냥한 일련의 제안을 커뮤니티에 공개했다.
왜 지금인가
이 작업이 지금 가능해진 배경에는 두 번의 언어 확장이 있다. Go 1.18의 제네릭과 1.23의 이터레이터가 그것이다. 제네릭 덕분에 라이브러리가 정의한 타입도 내장 타입에 준하는 사용성을 갖출 수 있게 됐고, 이터레이터 덕분에 슬라이스·맵에 대한 흔한 연산을 라이브러리 함수 호출로 자연스럽게 표현할 수 있게 됐다. 워킹그룹은 단순히 자료구조 몇 개를 추가하는 데 그치지 않고, 이번 추가분과 앞으로 들어올 컬렉션들이 공유할 API 관례를 세우는 것을 목표로 삼는다. 참고로 이들은 리눅스의 컨테이너 가상화 개념과의 혼동을 피하려고 '컨테이너' 대신 '컬렉션'이라는 용어를 선호하지만, 새 패키지들은 기존 container 트리 아래에 자리 잡는다.
무엇이 들어오나
제안의 출발점은 이미 go1.27에 반영된 hash/maphash.Hasher다. 이는 사용자 정의 해시 함수와 동치 관계를 표현하는 표준 인터페이스로, 키가 슬라이스나 맵처럼 비교 불가능한 경우, 혹은 기본 비교가 원하는 결과를 주지 않는 경우(예컨대 깊은 비교가 필요한 types.Type)에 유용하다. 이 해시 인터페이스 위에 container/hash.Map과 container/hash.Set이 세워진다.
일반 실무자에게 더 체감이 큰 것은 container/set.Set일 것이다. 원소가 comparable인 집합을 위한 정식 타입으로, 내부적으로는 map[T]struct{}로 투명하게 표현되며 Union, Intersection 같은 표준 집합 연산을 지원한다. map[T]bool에서 false 값이 섞일 수 있다는 모호함을 없애 주기 때문에, 앞으로 대부분의 새 Go API에서 표준 집합으로 자리 잡을 것으로 워킹그룹은 기대한다. API를 바꿀 수 없는 기존 코드를 위해서는 레거시 집합을 그대로 다루는 헬퍼 함수 묶음인 container/mapset이 별도로 제공된다. 여기에 균형 이진 트리 기반의 container/ordered.Map, 그리고 쓰기 까다로웠던 기존 힙을 대체하는 container/heap/v2.Heap이 더해진다. 삽입 순서를 유지하는 해시 맵이나 스택 같은 추가 제안은 이후 논의 대상이다.
인터페이스 대신 관례를 택한 이유
설계에서 흥미로운 대목은 추상 컬렉션 인터페이스를 공개하지 않기로 한 결정이다. 각 집합 타입 S가 func (S) Union(S) S 형태의 메서드를 가지면, 서로 다른 집합 타입의 Union끼리는 호환되지 않는다. 이른바 '이항 메서드 문제(binary method problem)'다. 이를 일반적인 인터페이스로 묶으려면 F-바운드 다형성, 즉 재귀적 제약 인터페이스가 필요하다. 워킹그룹은 CL 761460에서 비공개(unexported) 추상 Collection·Set·Map 제약 타입을 두어 테스트에서 일관성을 검증하는 용도로만 쓰기로 했다. 지금 공개하지 않는 이유는 어떤 메서드 집합이 옳은지 실제 사용 경험을 통해 배우겠다는 것이다.
어떤 연산을 인터페이스에 넣을지도 성능 관점에서 저울질했다. 예컨대 Subset은 정렬 집합이라도 흔한 경우 O(n)이라 인터페이스에서 뺐지만, DeleteFunc는 없으면 트리에서 조건부 삭제가 O(n log n)으로 나빠지기에 남겼다. Union 같은 집합 대수 연산은 새 집합을 반환하는 순수 함수형이 기본이고, 왼쪽 피연산자를 직접 변형하는 -With 변형이 짝으로 제공된다. 하나로 합치지 않은 것은 math/big.Int API의 경험에서 얻은 교훈으로, 결과를 잊고 쓰거나 실수로 원본을 변형하는 위험을 피하기 위함이다.
실무적 의미와 한계
실무 입장에서 이번 변화의 핵심은 '표준화'다. 그동안 프로젝트마다 제각각 만들던 집합 유틸리티와 정렬 맵이 표준 API로 수렴하면, 코드 리뷰와 라이브러리 간 상호운용의 마찰이 줄어든다. 특히 set.Set이 새 API의 관용 표현으로 자리 잡으면 map[T]bool 기반 코드의 모호함에서 벗어날 수 있다. 다만 아직은 제안 단계이며 Go 1.28 확정 여부는 논의 결과에 달려 있다는 점을 감안해야 한다. 또한 워킹그룹 스스로 밝혔듯 초기 구현은 API와 점근 성능 기대치를 가장 단순하게 만족시키는 데 초점을 맞췄고, 상수 항을 줄이는 최적화는 제안 범위 밖으로 미뤄 두었다. 추상 인터페이스를 공개하지 않기로 한 만큼, 여러 집합 타입을 아우르는 제네릭 코드를 쓰려면 당분간은 각자 최소한의 제약 타입을 직접 정의해야 한다.