はじめに
[]int
でも []string
でも、どのような型の配列でも、どのような型のスライスでも、引数として受け取れる関数を作りたいことがありました。
作ろうとしたら一筋縄ではいかなかったので、その方法を書き留めておきます。
環境
% go version
go version go1.17.6 darwin/amd64
失敗例
はじめに、引数の型に interface{}
を使うとなんでも受け取れるので []interface{}
を使えばどのような型の配列・スライスでも受け取れるだろうと思い、以下のコードを書きました。
package main
import (
"fmt"
)
func printElements(array []interface{}) { // []int や []string など、配列・スライスならなんでも受け取りたい
for _, e := range array {
fmt.Println(e)
}
}
func main() {
numbers := []int{2, 3, 5, 7, 11}
printElements(numbers)
texts := []string{"foo", "bar", "baz", "qux", "quux"}
printElements(texts)
}
./main.go:15:15: cannot use numbers (type []int) as type []interface {} in argument to printElements
./main.go:18:15: cannot use texts (type []string) as type []interface {} in argument to printElements
しかし、これを実行しようとするとコンパイルに失敗します。
どうやら []interface{}
型の引数は interface{}
型の配列・スライスしか受け取れないようです。
解決方法
やっていることの本質は同じですが、一応2パターンあるので分けて記載します。
引数の型に interface{}
を使用する
どのような型の配列・スライスでも受け取れるように、引数の型をなんでも受け取れる interface{}
にします。
そして、リフレクションを使用して引数の値を判別し、配列またはスライスでない場合はエラーを返すようにします。
package main
import (
"fmt"
"reflect"
)
func printElements(array interface{}) error {
arr := reflect.ValueOf(array)
if arr.Kind() != reflect.Array && arr.Kind() != reflect.Slice {
return fmt.Errorf(`"%#v" is not array`, array)
}
for i := 0; i < arr.Len(); i++ {
fmt.Println(arr.Index(i))
}
return nil
}
func main() {
numbers := []int{2, 3, 5, 7, 11}
_ = printElements(numbers)
texts := []string{"foo", "bar", "baz", "qux", "quux"}
_ = printElements(texts)
}
2
3
5
7
11
foo
bar
baz
qux
quux
配列・スライスを []interface{}
型に変換する
まず、どのような型の配列・スライスでも []interface{}
型に変換する関数を用意します。
この関数はリフレクションを使用して引数の値を判別し、配列またはスライスでない場合にエラーを返します。
そうしたら、行いたい処理は []interface{}
型を引数にとる関数として実装します。
package main
import (
"fmt"
"reflect"
)
func convert(array interface{}) ([]interface{}, error) {
arr := reflect.ValueOf(array)
if arr.Kind() != reflect.Array && arr.Kind() != reflect.Slice {
return nil, fmt.Errorf(`"%#v" is not array`, array)
}
res := make([]interface{}, arr.Len())
for i := 0; i < arr.Len(); i++ {
res[i] = arr.Index(i).Interface()
}
return res, nil
}
func printElements(array []interface{}) {
for _, e := range array {
fmt.Println(e)
}
}
func main() {
numbers := []int{2, 3, 5, 7, 11}
ns, _ := convert(numbers)
printElements(ns)
texts := []string{"foo", "bar", "baz", "qux", "quux"}
ts, _ := convert(texts)
printElements(ts)
}
2
3
5
7
11
foo
bar
baz
qux
quux
おわりに
これで便利にどんな配列・スライスでも扱えますね。
リフレクション様様です。