10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Go】[]string型は[]interface{}型ではない!

Last updated at Posted at 2023-02-21

はじめに

こんにちは、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))
}
output
こんにちは、僕の名前は野比のび太です。
こんにちは、私の名前は源静香です。
こんにちは、ぼくドラえもんです。

このサンプルコードは以下のリンクから実行することができます。

そして空インターフェースというのは任意の型に互換性のあるインターフェースのこと です。
インターフェースというのはメソッドの集まりによって定義されるものでしたが、空インターフェースはその定義に使われるメソッドが一つもない、つまりなにもメソッドを実装していなくても空インターフェースの要件は満たされるため任意の型は空インターフェースを実装しているといえる、ということです。
よって、次のように空インターフェースで宣言された変数にはどんな型を代入してもエラーは起きません。

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

ここまで読んでいただき、ありがとうございました。間違いなどあればコメントにてご指摘いただけますと幸いです。

  1. 「RobotはHumanじゃなくね?」という指摘はもっともなんですが今回は気にしないでください……

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?