Go 제네릭: 모든 것을 알아야 할 필요가 있다
Daniel Hayes
Full-Stack Engineer · Leapcell

제네릭이란 무엇인가
제네릭 프로그래밍은 프로그래밍 언어의 스타일 또는 패러다임입니다. 제네릭을 사용하면 프로그래머가 나중에 지정되는 일부 유형을 사용하여 강력한 형식의 프로그래밍 언어로 코드를 작성할 수 있으며 이러한 유형은 인스턴스화 중에 매개변수로 제공됩니다.
제네릭을 사용하면 각 유형에 대해 동일한 논리를 반복하지 않고도 여러 유형에 적용할 수 있는 코드를 작성할 수 있습니다. 이렇게 하면 코드 재사용성, 유연성 및 유형 안전성이 향상됩니다.
Go에서 제네릭은 유형 매개변수를 통해 구현됩니다. 유형 매개변수는 모든 유형이 될 수 있는 자리 표시자로 사용되는 특수한 종류의 매개변수입니다. 이는 함수, 메서드 및 유형의 정의에 사용되며 구체적인 호출 중에 특정 유형으로 대체됩니다.
제네릭 이전
두 개의 int
매개변수를 사용하고 둘 중 더 작은 값을 반환하는 함수를 구현하라는 요구 사항을 고려해 보세요. 이는 매우 간단한 요구 사항이며 다음 코드를 쉽게 작성할 수 있습니다.
func Min(a, b int) int { if a < b { return a } return b }
이것은 훌륭해 보이지만 함수에는 한계가 있습니다. 매개변수는 int
유형만 가능합니다. 요구 사항이 확장되어 두 개의 float64
값의 비교를 지원하고 더 작은 값을 반환해야 하는 경우 다음과 같이 작성할 수 있습니다.
func Min(a, b int) int { if a < b { return a } return b } func MinFloat64(a, b float64) float64 { if a < b { return a } return b }
요구 사항이 확장되는 즉시 변경해야 하고 반복적인 작업을 계속해야 한다는 것을 알 수 있습니다. 제네릭은 바로 이 문제를 해결합니다.
import "golang.org/x/exp/constraints" func Min[T constraints.Ordered](x, y T) T { if x < y { return x } return y }
제네릭의 기본 구문
// 함수 정의 func F[T any](p T){...} // 유형 정의 type M[T any] []T // Constraint는 any, comparable과 같은 특정 유형 제약 조건을 나타냅니다. func F[T Constraint](p T){..} // “~” 기호는 기본 유형 제약 조건을 나타내는 데 사용됩니다. type E interface { ~string } // 여러 유형 지정 type UnionElem interface { int | int8 | int32 | int64 }
Go 제네릭에서 ~
기호는 기본 유형 제약 조건을 나타내는 데 사용됩니다.
예를 들어 ~int
는 기본 유형이 int
인 모든 유형(사용자 지정 유형 포함)을 허용한다는 의미입니다. 기본 유형이 int
인 사용자 지정 유형 MyInt
가 있는 경우 이 제약 조건은 MyInt
유형을 허용할 수 있습니다.
type MyInt int type Ints[T int | int32] []T func main() { a := Ints[int]{1, 2} // 올바름 b := Ints[MyInt]{1, 2} // 컴파일 오류 println(a) println(b) }
MyInt는 int | int32를 충족하지 않습니다(int | int32에서 int에 대해 ~가 누락되었을 수 있음)compilerInvalidTypeArg
다음과 같이 수정하기만 하면 됩니다.
type Ints[T ~int | ~int32] []T
유형 제약 조건
any
: 모든 유형 허용comparable
:==
및!=
연산 지원ordered
:>
、<
와 같은 비교 연산자 지원
다른 유형은 다음을 참조하세요. https://pkg.go.dev/golang.org/x/exp/constraints
제네릭을 사용해야 하는 경우
제네릭을 사용해야 하는 경우
- 언어 정의 컨테이너 유형에 대한 작업의 경우: 슬라이스, 맵 및 채널과 같이 언어에서 정의한 특수 컨테이너 유형에 대해 작동하는 함수를 작성하고 함수 코드가 요소 유형에 대해 특정 가정을 하지 않는 경우 유형 매개변수를 사용하는 것이 유용할 수 있습니다. 예를 들어 모든 유형의 맵에서 모든 키의 슬라이스를 반환하는 함수입니다.
- 범용 데이터 구조: 연결 목록 또는 이진 트리와 같은 범용 데이터 구조의 경우 유형 매개변수를 사용하면 보다 일반적인 데이터 구조를 생성하거나 데이터를 보다 효율적으로 저장하고 유형 어설션을 피하고 빌드 시 전체 유형 검사를 수행할 수 있습니다.
- 제네릭 메서드 구현: 서로 다른 유형이 몇 가지 공통 메서드를 구현해야 하고 이러한 구현이 정확히 동일해 보이는 경우 유형 매개변수를 사용하는 것이 유용할 수 있습니다. 예를 들어 모든 슬라이스 유형에 대해
sort.Interface
를 구현하는 제네릭 유형입니다. - 메서드보다 함수 선호: 비교 함수와 같은 것이 필요한 경우 메서드 대신 함수를 사용하는 것이 좋습니다. 범용 데이터 유형의 경우 메서드를 요구하는 제약 조건을 작성하는 대신 함수를 사용하는 것이 좋습니다.
// SliceFn은 T 슬라이스에 대해 sort.Interface를 구현합니다. type SliceFn[T any] struct { s []T less func(T, T) bool } func (s SliceFn[T]) Len() int { return len(s.s) } func (s SliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] } func (s SliceFn[T]) Less(i, j int) bool { return s.less(s.s[i], s.s[j]) }
// SortFn은 비교 함수를 사용하여 s를 제자리에서 정렬합니다. func SortFn[T any](s []T, less func(T, T) bool) { sort.Sort(SliceFn[T]{s, less}) }
제네릭을 사용하지 않아야 하는 경우
- 인터페이스 유형을 대체하지 마십시오: 특정 유형의 값에 대해 메서드만 호출해야 하는 경우 유형 매개변수 대신 인터페이스 유형을 사용해야 합니다. 예를 들어 인터페이스 유형을 사용하는 함수를 유형 매개변수를 사용하는 함수로 변경해서는 안 됩니다.
- 메서드 구현이 다른 경우 유형 매개변수를 사용하지 마십시오: 유형마다 메서드 구현이 다른 경우 유형 매개변수를 사용하는 대신 인터페이스 유형을 사용하고 다른 메서드 구현을 작성해야 합니다.
- 반사를 적절하게 사용하십시오: 일부 작업에서 메서드가 없는 유형까지 지원해야 하고 작업이 유형마다 다른 경우 반사를 사용합니다. 예를 들어
encoding/json
패키지는 반사를 사용합니다.
간단한 지침
사용하는 유형만 다르고 정확히 동일한 코드를 여러 번 작성하는 경우 유형 매개변수를 사용하는 것을 고려할 수 있습니다. 즉, 정확히 동일한 코드를 여러 번 작성하려고 한다는 것을 알 때까지 유형 매개변수를 사용하지 않아야 합니다.
여담
다른 언어에서 흔히 사용되는 꺾쇠 괄호 < >
대신 대괄호 []
를 사용하는 이유는 무엇입니까?
https://github.com/golang/proposal/blob/master/design/15292/2013-12-type-params.md
벡터에서와 같이 꺾쇠 괄호를 사용합니다. 이는 C++ 및 Java 프로그래머에게 익숙하다는 장점이 있습니다. 불행히도 이것은 f(true)가 함수 f에 대한 호출 또는 f<T(f가 T보다 작은지 테스트하는 표현식)와 (true)의 비교로 구문 분석될 수 있음을 의미합니다. 복잡한 해결 규칙을 구성할 수 있지만 Go 구문은 타당한 이유 때문에 그러한 모호성을 피합니다.
Leapcell은 Go 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하세요.
무제한 프로젝트를 무료로 배포하세요
- 사용량에 대해서만 비용을 지불합니다. 요청도 없고 요금도 없습니다.
최고의 비용 효율성
- 유휴 요금 없이 종량제 방식으로 지불합니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI입니다.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합입니다.
- 실행 가능한 통찰력을 위한 실시간 지표 및 로깅입니다.
손쉬운 확장성과 고성능
- 자동 확장을 통해 높은 동시성을 쉽게 처리할 수 있습니다.
- 운영 오버헤드가 전혀 없습니다. 구축에만 집중하십시오.
설명서에서 자세히 알아보세요!
X에서 팔로우하세요: @LeapcellHQ