Generics(ジェネリクス)の使い方(comparable)

Go 1.18でGenerics(ジェネリクス)が追加されました。

Genericsを利用すると「型が異なるだけで同じ処理をもつ複数の関数」を「1つの関数」として定義することができます。

ここでは、Generics(ジェネリクス)の基本的な使い方を確認します。

目次

動作確認用コード
( Genericsを使わない )

まず、Genericsを使わない状態のコードです。型ごとに関数を用意しています。

package main

import (
	"fmt"
)

func main() {
	stringValue := "111"
	intValue := 111
	boolValue := false

	fmt.Printf("sampleFuncString: %#v\n", sampleFuncString(stringValue))
	fmt.Printf("sampleFuncInt: %#v\n", sampleFuncInt(intValue))
	fmt.Printf("sampleFuncBool: %#v\n", sampleFuncBool(boolValue))
}

func sampleFuncString(x string) string {
	return x
}

func sampleFuncInt(x int) int {
	return x
}

func sampleFuncBool(x bool) bool {
	return x
}
sampleFuncString: "111"
sampleFuncInt: 111
sampleFuncBool: false

Genericsの使い方

comparableで型制約

以下、先述のコードをGenericsを利用して1つの関数で実装してみました。

package main

import (
	"fmt"
)

func main() {
	stringValue := "111"
	intValue := 111
	boolValue := false

	fmt.Printf("sampleFuncGenerics1: %#v\n", sampleFuncGenerics1(stringValue))
	fmt.Printf("sampleFuncGenerics1: %#v\n", sampleFuncGenerics1(intValue))
	fmt.Printf("sampleFuncGenerics1: %#v\n", sampleFuncGenerics1(boolValue))
}

func sampleFuncGenerics1[T comparable](x T) T {
	return x
}
sampleFuncGenerics1: "111"
sampleFuncGenerics1: 111
sampleFuncGenerics1: false

comparable は、型パラメータを制約したいときにだけ利用できます。

sampleFuncGenerics1 の引数に指定できる値ですが、 ==, !=演算子で比較可能な値のみになります。
以下のように、スライスなどは ==演算子 で比較不可能なので利用できません。

複数の型で型制約

型パラメータを string または int だけに制約したい場合、以下のように実装できます。

package main

import (
	"fmt"
)

func main() {
	stringValue := "111"
	intValue := 111

	fmt.Printf("sampleFuncGenerics2: %#v\n", sampleFuncGenerics2(stringValue))
	fmt.Printf("sampleFuncGenerics2: %#v\n", sampleFuncGenerics2(intValue))
}

func sampleFuncGenerics2[T string | int](x T) T {
	return x
}
sampleFuncGenerics2: "111"
sampleFuncGenerics2: 111

独自インタフェースで型制約

型パラメータを string または int だけに制約したい場合、以下のようにインタフェースを定義して実装することも可能です。

package main

import (
	"fmt"
)

func main() {
	stringValue := "111"
	intValue := 111

	fmt.Printf("sampleFuncGenerics3: %#v\n", sampleFuncGenerics3(stringValue))
	fmt.Printf("sampleFuncGenerics3: %#v\n", sampleFuncGenerics3(intValue))
}

type SampleType interface {
	string | int
}

func sampleFuncGenerics3[T SampleType](x T) T {
	return x
}
sampleFuncGenerics3: "111"
sampleFuncGenerics3: 111

Underlying Typeで型制約
( ~ | チルダ )

以下例では [T ~int] といった形で ~ を利用して型制約しています。

package main

import (
	"fmt"
)

type SampleType1 int // underlying typeがint
type SampleType2 int // underlying typeがint

func main() {
	intValue1 := 111
	intValue2 := SampleType1(222)
	intValue3 := SampleType2(333)

	fmt.Printf("sampleFuncGenerics4: %#v\n", sampleFuncGenerics4(intValue1))
	fmt.Printf("sampleFuncGenerics4: %#v\n", sampleFuncGenerics4(intValue2))
	fmt.Printf("sampleFuncGenerics4: %#v\n", sampleFuncGenerics4(intValue3))
}

func sampleFuncGenerics4[T ~int](x T) T {
	return x
}

~ を利用することで、Underlying Type(基礎型) で型制約できます。

上記例では Underlying Typeがintの型のみ許可 しています。

参考

よかったらシェアしてね!
目次