はじめに
こんにちは、kenです。
最近、Goの空インターフェースについて勘違いをしていたことに気づいたので記事にします。
要点は表題の通り、「[]string型は[]interface{}型ではない!」ということです。
空インターフェースとは
そもそも空インターフェースとは何なのかについておさらいしておきます。すでに知ってるよという方は飛ばして貰ってOKです。
まずインターフェースというのはなにかというと「メソッドの集まりにより定義される、構造体の抽象度をより高くした型」のことです。
下のサンプルコードを見てください。ここではHuman
インターフェースがSelfIntroduction()
というメソッドにより定義されており、Man
型・Woman
型・Robot
型はそれぞれSelfIntroduction()
というメソッドを持っているので、暗黙的にこの3つの型はHuman
インターフェースを実装しています。1
これによりHuman
インターフェースの型を引数にもつHello()
関数は、渡される型が何であるかを気にすることなく統一的に扱うことができるため、簡潔に処理を書くことができています。
package main
import "fmt"
type Man struct {
Name string
}
type Woman struct {
Name string
}
type Robot struct {
Name string
}
type Human interface {
SelfIntroduction() string
}
func (m Man) SelfIntroduction() string {
return fmt.Sprintf("僕の名前は%sです。", m.Name)
}
func (w Woman) SelfIntroduction() string {
return fmt.Sprintf("私の名前は%sです。", w.Name)
}
func (dora Robot) SelfIntroduction() string {
return fmt.Sprintf("ぼく%sです。", dora.Name)
}
func Hello(h Human) string {
return fmt.Sprint("こんにちは、", h.SelfIntroduction())
}
func main() {
Nobita := Man{
Name: "野比のび太",
}
Shizuka := Woman{
Name: "源静香",
}
Dora := Robot{
Name: "ドラえもん",
}
fmt.Println(Hello(Nobita))
fmt.Println(Hello(Shizuka))
fmt.Println(Hello(Dora))
}
こんにちは、僕の名前は野比のび太です。
こんにちは、私の名前は源静香です。
こんにちは、ぼくドラえもんです。
このサンプルコードは以下のリンクから実行することができます。
そして空インターフェースというのは任意の型に互換性のあるインターフェースのこと です。
インターフェースというのはメソッドの集まりによって定義されるものでしたが、空インターフェースはその定義に使われるメソッドが一つもない、つまりなにもメソッドを実装していなくても空インターフェースの要件は満たされるため任意の型は空インターフェースを実装しているといえる、ということです。
よって、次のように空インターフェースで宣言された変数にはどんな型を代入してもエラーは起きません。
var obj interface{}
obj = 1 // int
obj = "hoge" // string
obj = false // bool
obj = []string{1, 2, 3} // []string
本題へ
ではここからは過去の私がしていた勘違いについて見ていきます。
先ほど私は
空インターフェースというのは任意の型に互換性のあるインターフェースのこと
といいましたね。そして
任意の型は空インターフェースを実装している
空インターフェースで宣言された変数にはどんな型を代入してもエラーは起きません
ともいいました。よってstring
型はもちろん空インターフェースinterface{}
型でもあります。
それなら[]string
型は[]interface{}
型とみなせますよね。なぜなら[]string
型はinterface{}
型でもあるstring
型を並べたスライスなのだから……
はい、これ間違いです。。。
実際には[]string
型は[]interface{}
型ではありません
私がこの勘違いをしたのは次のようなコードを書いているときです。
[]interface{}
型を引数にとる関数に[]string
型を渡したところエラーが起きてしまいました。
package main
func hoge(objs []interface{}) {
// なんらかの処理
}
func main() {
var str []string = []string{"foo", "bar"}
hoge(str) // ここでエラー
}
cannot use str (variable of type []string) as []interface{} value in argument to hoge
最初このエラーメッセージを読んだときは
「えっ、[]string
型が[]interface{}
型ではないって、そんなわけなくない??」
と思いました。(そんなわけありました)
不思議に思っていたところ先輩から次のサイトを教えていただきました。
そのなかの次の記述が言い得て妙だと思ったので引用します。
まず1つは、[]interface{}の変数はインターフェースではありません!
これは要素型がたまたまinterface{}のスライスなのです。
た、たしかに。。。
ではどうすればよかったのかというと、これもリンク先のサイトに答えがあります。ただスライスの個々の要素を空インターフェース型へと変換するだけです。
package main
func hoge(objs []interface{}) {
// なんらかの処理
}
func main() {
var str []string = []string{"foo", "bar"}
var objs []interface{}
for i, s := range str {
objs[i] = s //ここで個々の要素をinterface{}型へ
}
hoge(objs) // エラーが起きない!
}
まとめ
Goは静的型付け言語です。型がぴったり同じでなかったら、エラーで教えてくれます。
そして[]interface{}
は要素がinterface{}
のスライスで、これはインターフェースでなくれっきとした型です。それならば[]string
とは型が違うと、エラーで教えてくれるのも納得ですね。勉強になりました。
おまけ
空インターフェースの配列に限らず、あるインターフェースを実装している型のスライスをそのインターフェースのスライスとして扱いたい場合は、明示的に型変換してからでないとエラーが起きます。
(なんとなく、これは「まあそうなるだろうな」という気持ちになりました。空インターフェースの場合だけ勘違いをしてしまうのは、空インターフェースの任意の型と互換性をもつという強烈なインパクトが原因なんでしょうかね。。。)
package main
import "fmt"
type Man struct {
Name string
}
type Human interface {
SelfIntroduction() string
}
func (m Man) SelfIntroduction() string {
return fmt.Sprintf("僕の名前は%sです。", m.Name)
}
func Hello(h Human) string {
return fmt.Sprint("こんにちは、", h.SelfIntroduction())
}
func PrintHello(hs []Human) {
for _, h := range hs {
fmt.Println(Hello(h))
}
}
func main() {
Nobita := Man{
Name: "野比のび太",
}
Gian := Man{
Name: "剛田武",
}
Suneo := Man{
Name: "骨川スネ夫",
}
var Men []Man = []Man{
Nobita, Gian, Suneo,
}
PrintHello(Men) // ここでエラーが出る
}
cannot use Men (variable of type []Man) as []Human value in argument to PrintHello
ここまで読んでいただき、ありがとうございました。間違いなどあればコメントにてご指摘いただけますと幸いです。
-
「RobotはHumanじゃなくね?」という指摘はもっともなんですが今回は気にしないでください…… ↩