Go 1.18에 도입된 제네릭은 재사용 가능하고 유형이 안전한 코드를 작성하는 방식에 혁명을 일으켰습니다. Generics는 Go의 단순성 철학을 유지하면서 유연성과 강력함을 제공합니다. 그러나 미묘한 차이, 이점 및 일반 접근 방식(예: 인터페이스{})과 일반 접근 방식을 비교하는 방법을 이해하려면 자세히 살펴봐야 합니다.
제네릭의 복잡성을 살펴보고, 제약 조건을 자세히 살펴보고, 제네릭과 인터페이스를 비교하고, 실제 적용 사례를 보여드리겠습니다. 또한 성능 고려 사항과 바이너리 크기에 미치는 영향에 대해서도 다룰 것입니다. 뛰어들어 보세요!
제네릭을 사용하면 개발자는 유형 안전성을 유지하면서 모든 유형에서 작동할 수 있는 함수와 데이터 구조를 작성할 수 있습니다. 런타임에 유형 어설션을 포함하는 인터페이스{}에 의존하는 대신, 제네릭을 사용하면 유형에 허용되는 작업을 지시하는 제약 조건 집합을 지정할 수 있습니다.
구문
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
T: 유형에 대한 자리 표시자를 나타내는 유형 매개변수입니다.
TypeConstraint: T의 유형을 특정 유형 또는 유형 집합으로 제한합니다.
parameterName T: 매개 변수는 일반 유형 T을 사용합니다.
ReturnType: 이 함수는 T 유형의 값을 반환할 수도 있습니다.
예
func Sum[T int | float64](a, b T) T { return a + b }
func Sum: 함수 이름 Sum을 선언합니다
[T int | float64]: T를 특정 유형(int 또는 float64)으로 제한되는 유형 매개변수로 소개하는 유형 매개변수 목록을 지정합니다. Sum 함수는 int 또는 float64 매개변수만 취할 수 있으며 조합은 불가능합니다. 둘 다 int 또는 float64 중 하나여야 합니다. 아래 섹션에서 이에 대해 더 자세히 살펴보겠습니다.
(a, b T): 두 매개변수 a와 b를 모두 T 유형(일반 유형)으로 선언합니다. ).
T: 유형 매개변수 T.
와 일치하는 함수의 반환 유형을 지정합니다.제약조건은 일반 유형에 유효한 작업을 정의합니다. Go는 실험적 제약 조건 패키지(golang.org/x/exp/constraints)를 포함하여 제약 조건을 위한 강력한 도구를 제공합니다.
Go에서는 유형 안전성을 제공하는 동시에 재사용 가능한 일반 코드를 유연하게 정의할 수 있도록 제네릭에 내장된 제약 조건을 도입했습니다. 이러한 제약 조건을 통해 개발자는 일반 함수 또는 유형에 사용되는 유형에 규칙을 적용할 수 있습니다.
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
func Sum[T int | float64](a, b T) T { return a + b }
실험적 제약
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
사용자 정의 제약 조건은 일반 유형 매개변수가 충족해야 하는 유형 또는 유형 동작 집합을 정의하는 인터페이스입니다. 자신만의 제약 조건을 만들어서 다음을 수행할 수 있습니다.
숫자 유형과 같은 특정 하위 집합으로 유형을 제한합니다.
특정 메서드나 동작을 구현하려면 유형이 필요합니다.
일반 함수와 유형에 더 많은 제어 기능과 특수성을 추가하세요.
구문
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
예
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
Sum 함수는 int, int64 및 float64 매개변수만 사용하여 호출할 수 있습니다.
특정 메소드를 구현해야 하는 유형을 적용하려면 해당 메소드를 사용하여 정의할 수 있습니다.
type Numeric interface { int | float64 | uint }
Formatter 제약 조건에서는 T로 사용되는 모든 유형에 문자열.
제약 조건 결합
type Number interface { int | int64 | float64 } func Sum[T Number](a, b T) T { return a + b }
int, float54)이 모두 포함되며 abs 메서드가 필요합니다.
제네릭 대 인터페이스{}유형 안전
인터페이스{}: 추가 런타임 유형 확인으로 인해 속도가 느려집니다.
제네릭: 컴파일러가 유형별로 최적화된 코드 경로를 생성하므로 더 빠릅니다.
인터페이스{}: 종종 장황하고 덜 직관적이어서 코드를 유지 관리하기가 더 어렵습니다.
일반: 구문이 깔끔해지면 코드가 더 직관적이고 유지 관리가 쉬워집니다.
인터페이스{}: 다양한 유형에 대해 코드를 복제하지 않으므로 더 작은 바이너리가 생성됩니다.
일반: 더 나은 성능을 위해 유형 전문화로 인해 바이너리 크기가 약간 증가합니다.
예
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
코드는 잘 작동하지만 유형 어설션이 오버헤드입니다. Add 함수는 어떤 인수로도 호출할 수 있습니다. a와 b 매개변수는 모두 서로 다른 유형일 수 있지만 코드는 런타임에 충돌합니다.
func Sum[T int | float64](a, b T) T { return a + b }
제네릭은 잘못된 유형 어설션으로 인한 런타임 패닉 위험을 제거하고 명확성을 향상시킵니다.
제네릭은 각 유형에 대해 특화된 코드를 생성하므로 인터페이스에 비해 런타임 성능이 향상됩니다{}.
장충점이 존재합니다. 제네릭은 각 유형의 코드 중복으로 인해 바이너리 크기를 늘리지만 이는 이점에 비해 무시할 수 있는 경우가 많습니다.
제약조건의 복잡성: Constraints.Ordered와 같은 제약조건은 일반적인 사용 사례를 단순화하지만 고도로 맞춤화된 제약조건을 정의하면 장황해질 수 있습니다.
구조체에 유형 추론 없음: 함수와 달리 구조체에는 유형 매개변수를 명시적으로 지정해야 합니다.
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
컴파일 시간 제약 조건으로 제한: Go 제네릭은 컴파일 시간 안전에 중점을 두는 반면, Rust와 같은 언어는 수명과 특성을 사용하여 더 강력한 제약 조건을 제공합니다.
인터페이스와 일반을 모두 사용하여 간단한 대기열을 구현하고 결과를 벤치마킹하겠습니다.
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
type Numeric interface { int | float64 | uint }
실행 시간:
일반 구현은 런타임 유형 어설션을 피하고 지정된 유형에서 직접 작동하기 때문에 인터페이스 버전보다 약 63.64% 빠릅니다.
할당:
인터페이스{} 버전은 주로 값을 삽입하고 검색할 때 박싱/언박싱으로 인해 3배 더 많은 할당을 수행합니다. 이로 인해 가비지 수집에 오버헤드가 추가됩니다.
100만 개의 대기열 추가/제거 작업과 같은 대규모 워크로드의 경우 성능 격차가 더 커집니다. 처리량이 많은 실제 애플리케이션(예: 메시지 대기열, 작업 스케줄러)은 제네릭의 이점을 크게 누릴 수 있습니다.
Go의 Generics는 성능과 단순성 사이의 균형을 유지하며 재사용 가능하고 유형이 안전한 코드를 작성하기 위한 실용적인 솔루션을 제공합니다. Rust나 C만큼 기능이 풍부하지는 않지만 Go의 미니멀리스트 철학과 완벽하게 일치합니다. 제약 조건과 같은 제약 조건을 이해합니다. 제네릭을 효과적으로 정렬하고 활용하면 코드 품질과 유지 관리성이 크게 향상됩니다.
제네릭이 계속 발전함에 따라 Go 생태계에서 중심 역할을 하게 될 것입니다. 따라서 Go 프로그래밍의 유형 안전성과 유연성의 새로운 시대를 경험하고 실험하고 수용해 보세요!
Github 저장소에서 제네릭에 대한 일부 샘플을 확인하세요.
Go Generics Repository에 오신 것을 환영합니다! 이 저장소는 버전 1.18에 도입된 Go의 제네릭을 이해하고 학습하고 마스터하기 위한 원스톱 리소스입니다. Generics는 유형 매개변수의 강력한 기능을 Go에 가져오므로 개발자는 성능이나 가독성 저하 없이 재사용 가능하고 유형이 안전한 코드를 작성할 수 있습니다.
이 저장소에는 기본 구문부터 고급 패턴 및 실제 사용 사례에 이르기까지 광범위한 주제를 다루는 신중하게 선별된 예제가 포함되어 있습니다. 초보자이든 숙련된 Go 개발자이든 이 컬렉션은 프로젝트에서 제네릭을 효과적으로 활용하는 데 도움이 될 것입니다.
다음 예에서는 제네릭의 기본 개념을 소개하여 구문과 핵심 기능을 이해하는 데 도움을 줍니다.
위 내용은 Go의 제네릭: 코드 재사용성 혁신의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!