> 백엔드 개발 > Golang > Go의 제네릭: 코드 재사용성 혁신

Go의 제네릭: 코드 재사용성 혁신

Patricia Arquette
풀어 주다: 2025-01-08 06:20:41
원래의
492명이 탐색했습니다.

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.

와 일치하는 함수의 반환 유형을 지정합니다.

제약 조건: Generics의 빌딩 블록

제약조건은 일반 유형에 유효한 작업을 정의합니다. Go는 실험적 제약 조건 패키지(golang.org/x/exp/constraints)를 포함하여 제약 조건을 위한 강력한 도구를 제공합니다.

내장된 제약조건

Go에서는 유형 안전성을 제공하는 동시에 재사용 가능한 일반 코드를 유연하게 정의할 수 있도록 제네릭에 내장된 제약 조건을 도입했습니다. 이러한 제약 조건을 통해 개발자는 일반 함수 또는 유형에 사용되는 유형에 규칙을 적용할 수 있습니다.

Go에는 아래의 기본 제약 조건이 있습니다.

  1. any: 모든 유형을 나타냅니다. 이는 인터페이스{}의 별칭입니다. 제약조건이 필요하지 않을 때 사용됩니다.
func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
  1. 비교 가능: 동등 비교(== 및 !=)를 지원하는 유형을 허용합니다. 맵 키, 중복 감지 또는 동일성 검사에 유용합니다. 맵, 슬라이스 및 함수에는 이러한 유형이 직접 비교를 지원하지 않으므로 사용할 수 없습니다.
func Sum[T int | float64](a, b T) T {
    return a + b
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

실험적 제약

  1. constraints.Complex: 복합 숫자 유형(complex64 및 complex128)을 허용합니다.
  2. constraints.Float: 부동 소수점 숫자 유형(float32 및 float64)을 허용합니다
  3. constraints.Integer: 부호 있는 정수와 부호 없는 정수 모두 허용(int8, int16, int32, int64, int, uint8, uint16, uint32, uint64 및 uint)
  4. constraints.Signed: 모든 부호 있는 정수(int8, int16, int32, int64 및 int)를 허용합니다
  5. constraints.Unsigned: 부호 없는 정수(uint8, uint16, uint32, uint64 및 uint)를 허용합니다.
  6. constraint.Ordered: 비교가 가능한 유형(<. <=, >, >=)을 허용하며 모든 숫자 유형과 문자열이 지원됩니다(int, float64, string 등).
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
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

제네릭은 잘못된 유형 어설션으로 인한 런타임 패닉 위험을 제거하고 명확성을 향상시킵니다.

성능

제네릭은 각 유형에 대해 특화된 코드를 생성하므로 인터페이스에 비해 런타임 성능이 향상됩니다{}.

바이너리 크기

장충점이 존재합니다. 제네릭은 각 유형의 코드 중복으로 인해 바이너리 크기를 늘리지만 이는 이점에 비해 무시할 수 있는 경우가 많습니다.

Go 제네릭의 한계

제약조건의 복잡성: 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 저장소에서 제네릭에 대한 일부 샘플을 확인하세요.

GitHub logo 사다난도다와다카르 / GoGenerics

저장소에는 go generics의 작업 예제가 포함되어 있습니다.

Go Generics: 종합 예제 저장소

Go Generics Repository에 오신 것을 환영합니다! 이 저장소는 버전 1.18에 도입된 Go의 제네릭을 이해하고 학습하고 마스터하기 위한 원스톱 리소스입니다. Generics는 유형 매개변수의 강력한 기능을 Go에 가져오므로 개발자는 성능이나 가독성 저하 없이 재사용 가능하고 유형이 안전한 코드를 작성할 수 있습니다.

이 저장소에는 기본 구문부터 고급 패턴 및 실제 사용 사례에 이르기까지 광범위한 주제를 다루는 신중하게 선별된 예제가 포함되어 있습니다. 초보자이든 숙련된 Go 개발자이든 이 컬렉션은 프로젝트에서 제네릭을 효과적으로 활용하는 데 도움이 될 것입니다.


? 내용물

? 기본 일반 프로그램

다음 예에서는 제네릭의 기본 개념을 소개하여 구문과 핵심 기능을 이해하는 데 도움을 줍니다.

  1. GenericMap: 모든 유형의 슬라이스를 변환하는 일반 맵 기능을 보여줍니다.
  2. 교환: 일반적으로 두 값을 바꾸는 간단하면서도 강력한 예입니다.
  3. FilterSlice: 필터링 방법을 보여줍니다…


GitHub에서 보기


위 내용은 Go의 제네릭: 코드 재사용성 혁신의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿