본문 바로가기
Golang

[Golang] Go 언어에서 제네릭 메서드를 지원하지 않는 이유

by gungle 2024. 8. 7.

Go 언어는 제네릭을 도입하여 타입 파라미터를 사용할 수 있게 되었지만, 메서드에는 제네릭 타입 파라미터를 지원하지 않는다. 이 글에서는 Go 언어가 왜 제네릭 메서드를 지원하지 않는지, 그리고 그 결정의 이유와 대안에 대해 살펴본다.

 

제네릭 메서드란 무엇인가?

제네릭 메서드는 메서드 자체에 타입 파라미터를 사용하는 것을 의미한다. 이는 제네릭 타입을 가지고 있는 메서드를 정의할 수 있다는 것을 뜻한다. 예를 들어, 아래의 코드는 Nop이라는 제네릭 메서드를 가지는 Empty라는 타입을 정의한 것이다:

type Empty struct{}

func (Empty) Nop[T any](x T) T {
    return x
}

이 메서드는 인자로 받은 어떤 타입이든 그대로 반환한다.

 

자바의 제네릭 메서드 예시

자바에서는 제네릭 메서드를 지원하며, 아래와 같이 정의할 수 있다:

public class Example {
    public <T> T nop(T x) {
        return x;
    }

    public static void main(String[] args) {
        Example example = new Example();

        System.out.println(example.nop("hello")); // 출력: hello
        System.out.println(example.nop(42));      // 출력: 42
    }
}

자바의 nop 메서드는 타입 파라미터 T를 사용하여 어떤 타입의 인자도 받을 수 있으며, 이를 그대로 반환한다.

 

Go에서 제네릭 메서드를 지원하지 않는 이유

Go 언어는 제네릭 타입에 메서드를 가질 수 있도록 허용하지만, 메서드의 인자에는 제네릭 타입을 사용할 수 없다. 이는 Go 언어의 설계 원칙과 구현의 복잡성 때문인데, 이를 이해하기 위해 몇 가지 중요한 문제를 살펴보자.

 

인터페이스와의 호환성 문제

제네릭 메서드를 인터페이스와 함께 사용하는 경우를 생각해보자. 아래의 예시는 Empty 타입의 값을 any 타입으로 저장하고, 다양한 제네릭 타입의 Nop 메서드를 호출하는 코드이다:

func TryNops(x any) {
    if x, ok := x.(interface{ Nop(string) string }); ok {
        fmt.Printf("string %s\n", x.Nop("hello"))
    }
    if x, ok := x.(interface{ Nop(int) int }); ok {
        fmt.Printf("int %d\n", x.Nop(42))
    }
    if x, ok := x.(interface{ Nop(io.Reader) io.Reader }); ok {
        data, err := io.ReadAll(x.Nop(strings.NewReader("hello world")))
        fmt.Printf("reader %q %v\n", data, err)
    }
}

이 코드에서 x가 Empty 타입일 경우, Nop 메서드는 모든 타입에 대해 동작해야 한다. 이는 컴파일러가 모든 가능한 타입에 대해 메서드 구현을 생성하고 연결해야 함을 의미한다.

 

구현의 복잡성

제네릭 메서드를 지원하기 위한 구현 방법에는 몇 가지 선택지가 있다:

1. 링크 타임에 모든 가능한 인터페이스 체크 생성:

  • 이는 빌드 시간을 크게 증가시키고, 특히 증분 빌드에서 심각한 성능 저하를 초래한다.
  • 새로운 동적 인터페이스 체크가 생길 때마다 반복해야 하며, 최악의 경우 빌드가 끝나지 않을 수 있다.

2. JIT 컴파일:

  • Go 언어는 단순하고 예측 가능한 성능을 제공하기 위해 사전 컴파일 방식을 사용한다. JIT 컴파일은 이러한 단순성을 해치고, 구현의 복잡성을 증가시킨다.

3. 느린 대체 방법 사용:

  • 모든 제네릭 메서드에 대해 각 타입 파라미터에 대한 함수 테이블을 사용하는 느린 대체 방법을 구현할 수 있다. 이는 예측 가능한 성능을 저하시킨다.

4. 제네릭 메서드를 인터페이스에서 사용하지 않도록 정의:

  • 인터페이스는 Go 프로그래밍에서 중요한 부분이므로, 제네릭 메서드를 인터페이스에서 사용할 수 없도록 하는 것은 디자인 측면에서 받아들일 수 없다.

 

결론

위의 방법들 중 어느 것도 만족스럽지 않기 때문에, Go 언어 설계자들은 "제네릭 메서드를 지원하지 않음"이라는 결정을 내렸다. 대신, 타입 파라미터를 사용하는 최상위 함수 또는 타입 파라미터를 수신 타입에 추가하는 방법을 사용할 것을 권장한다.

 

해결 방안 예시: Hello World 출력

다음은 위의 해결 방안을 적용한 main.go 파일 예시이다. 이 예시는 제네릭 타입을 사용하여 Hello World를 출력하는 프로그램이다.

https://github.com/gunh0/to-be-a-gopher/blob/b7a64fcb5a7c2da945ae8d9b660e9bce939d840e/faq/simple_generic_return.go

 

to-be-a-gopher/faq/simple_generic_return.go at b7a64fcb5a7c2da945ae8d9b660e9bce939d840e · gunh0/to-be-a-gopher

🐁 Go is a statically typed, compiled programming language designed at Google. - gunh0/to-be-a-gopher

github.com

이 예시는 Empty 타입에 제네릭을 사용하여 다양한 타입의 Nop 메서드를 정의하고, 이를 통해 문자열, 정수, 그리고 io.Reader 타입의 값을 출력한다.

 

참고

https://go.dev/doc/faq