[Go] Generics 의 사용 및 예제

반응형

Go 언어는 강력한 타입 시스템과 고성능 컴파일러를 갖추고 있어, 안정성과 높은 성능을 보장해 줍니다. 그러나,

Go 언어는 제네릭 기능이 없어서, 유연성과 코드 재사용성 면에서 한계를 가지고 있습니다.

Go 1.18 버전에서 제네릭 기능을 제공하기 시작했습니다.

 

제네릭이란 무엇인가?

제네릭(Generic)은 일반화된 코드를 작성할 수 있게 해주는 프로그래밍 기법입니다. 제네릭은 코드의 재사용성을 높이고, 타입 안정성을 보장하여 코드의 안정성과 가독성을 향상시킵니다. Java, C++, C#과 같은 다양한 언어에서 제네릭 기능을 제공합니다.

 

 

제네릭의 장점은 무엇인가?

제네릭을 사용하면, 코드를 일반화할 수 있어서, 타입 별로 중복 코드를 작성하지 않아도 됩니다. 또한, 타입 안정성이 보장되므로, 런타임 시 타입 관련 오류가 발생하는 경우를 방지할 수 있습니다.

 

 

 

사용법

제네릭 함수(Generic function)는 함수 내에서 다양한 타입의 매개변수를 사용할 수 있도록 하는 함수입니다. Go 언어에서는 다음과 같은 형식으로 제네릭 함수를 작성할 수 있습니다.

func doSomething[T any](arg1 T, arg2 T) {
    // T 타입의 인자를 사용하여 함수 로직을 작성합니다.
}

위 예시 코드에서 [T any]는 제네릭 타입 매개변수입니다. T는 함수 내에서 사용될 타입을 나타내며, any 키워드는 모든 타입을 허용한다는 의미입니다. 함수 내에서 사용하는 모든 T 타입의 매개변수는 같은 타입으로 간주됩니다.

제네릭 타입(Generic type)은 여러 타입에서 동일한 동작을 수행할 수 있는 타입입니다. Go 언어에서는 제네릭 타입을 다음과 같은 형식으로 작성할 수 있습니다.

arduinoCopy code
type exampleType[T any] struct {
    // T 타입의 필드를 정의합니다.
}

위 예시 코드에서 [T any]는 제네릭 타입 매개변수입니다. T는 해당 타입에서 사용될 타입을 나타내며, any 키워드는 모든 타입을 허용한다는 의미입니다. 제네릭 타입을 사용하면, 타입 안정성이 보장되면서도 여러 타입에서 동일한 동작을 수행할 수 있습니다.

 

 

 

예시: Using Generics With Multiple Types

아래의 예시 코드에서는 제네릭 함수와 제네릭 타입을 사용하여, 다양한 타입에서 동작하는 코드를 작성합니다.

package main

import "fmt"

func swap[T any](a, b *T) {
    temp := *a
    *a = *b
    *b = temp
}

type Stack[T any] struct {
    data []T
}

func (s *Stack[T]) Push(elem T) {
    s.data = append(s.data, elem)
}

func (s *Stack[T]) Pop() T {
    if len(s.data) == 0 {
        panic("stack is empty")
    }
    elem := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return elem
}

func main() {
    var x, y int = 1, 2
    fmt.Println("before swap:", x, y)
    swap(&x, &y)
    fmt.Println("after swap:", x, y)

    var stackInt Stack[int]
    stackInt.Push(1)
    stackInt.Push(2)
    stackInt.Push(3)
    fmt.Println(stackInt.Pop()) // 3
    fmt.Println(stackInt.Pop()) // 2
    fmt.Println(stackInt.Pop()) // 1

    var stackStr Stack[string]
    stackStr.Push("hello")
    stackStr.Push("world")
    fmt.Println(stackStr.Pop()) // "world"
    fmt.Println(stackStr.Pop()) // "hello"
}

위 예시 코드에서 swap 함수는 int, float64, string 등 다양한 타입에서 동작하며, Stack 타입은 intstring 등 다양한 타입에서 사용할 수 있습니다. 이러한 방법으로 제네릭 함수와 제네릭 타입을 사용하여, Go 언어에서 제네릭을 구현할 수 있습니다.

 

 

 

 

 

예시: Restricting Generic Types

Go 언어에서는 인터페이스를 사용하여 제네릭 타입에 제한을 둘 수 있습니다. 이를 "Restricting Generic Types" 라고 합니다. 예를 들어, 다음과 같이 swap 함수를 구현할 때, T 타입에 제한을 두어서 swap 함수가 호출될 수 있는 타입을 제한할 수 있습니다.

package main

import "golang.org/x/exp/constraints"
import "fmt"

// 제네릭 함수: T는 숫자형 타입(int, float64 등)으로 제한
func swap[T interface{ Number }](a, b *T) {
    temp := *a
    *a = *b
    *b = temp
}

type Number interface {
    constraints.Float | constraints.Integer
}

func main() {
    x, y := 1, 2
    swap(&x, &y)
    fmt.Println(x, y) // 2 1

    a, b := 1.5, 2.5
    swap(&a, &b)
    fmt.Println(a, b) // 2.5 1.5
}

위 코드에서 interface{Number}T 타입이 숫자형 타입(int, float64 등)이어야 한다는 의미입니다. 따라서, swap 함수는 int, float64 등 숫자형 타입에서만 호출할 수 있습니다. 이러한 방법으로 제네릭 타입에 제한을 두어, 코드의 안정성과 가독성을 높일 수 있습니다.

 

 

string 타입은 swap함수가 호출이 불가능합니다.

 

 

참고