Go言語(golang) インターフェース interface{} の使い方
Go言語のinterface{}の役割は以下の2つです。
どんな型の値でも入れておける入れ物
(TypeScript の any型 や C言語の void型 のようなもの)構造体がメッソッド(関数)を持つことを保証するための型の定義
目次
1. どんな型の値でも入れておける入れ物
インターフェース型には int型、bool型、string型 など何の型でも代入できます。
同じようにスライスやマップでもinterface{}を使えばなんでも代入できる。
package main import "fmt" func main() { // 空のインターフェース var i interface{} i = 10 fmt.Println(i) // 10 i = true fmt.Println(i) // true i = "hello" fmt.Println(i) // hello fmt.Println(i == interface{}("hello")) // true // インターフェース型のスライス a := []interface{}{ 10, true, "hello", } fmt.Printf("%#v\n", a) // []interface {}{10, true, "hello"} // キーがstring型で値がインターフェース型のマップ m := make(map[string]interface{}) m["one"] = 10 m["two"] = true m["three"] = "hello" fmt.Printf("%#v\n", m) // map[string]interface {}{"one":10, "three":"hello", "two":true} }
具体的な使用例として fmt.Println は以下のような定義になっていて、
引数にどんな型の値でもいくつでも渡すことができます。
(内部的には reflect.TypeOf(i).String()
で文字列化して出力している)
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }
インターフェース型のままで、strings.ToUpper
などの引数に渡すと
cannot use i (type interface {}) as type string in argument to strings.ToUpper: need type assertion
というエラーになるので、メッセージにある通り型アサーションが必要です。
型アサーションについては Go言語(golang)のinterface{}を型アサーションする に書いています。
package main import ( "fmt" "strings" ) func main() { i := interface{}("hello") strings.ToUpper(i) // error }
2. 構造体がメッソッド(関数)を持つことを保証するための型の定義
基本的なinterface{}型の実装方法は、構造体が定義すべき関数を type I interface{ ... }
内に書くだけです。
あとは、構造体にインターフェースで定義した関数を実装します。インターフェースの要件を満たしていれば、
その構造体(以下の例では type S struct
) は、I型になることができます。
package main type I interface { A(string) B(name string) error C(age int, name string) D() (n int, err error) } type S struct { } func (s S) A(name string) { println(name) } func (s S) B(name string) error { return nil } func (s S) C(age int, name string) { } func (s S) D() (n int, err error) { return 0, nil } func main() { var i I var s S i = s i.A("hello") // hello }
保証するとは
以下の type I interface
は Print(string)
関数を持っていることを保証するための型です。
構造体 A には、 func (a A) Print(s string)
が定義してあるので、
Print(string)
という I型(インターフェース) の要件を満たしていることになります。
なので、構造体 A は type で定義してある通り型名は A ですが、
型として I を名乗る こともできるので、 func Output(i I)
関数に渡すことができます。
つまり func Output(i I)
関数の引数に渡された値は、 Print(string)
関数を持っていることが保証されるわけです。
package main // Print(string) を持っていることを保証する型の定義 type I interface { Print(string) } type A struct { } // 型 I の要件を満たす func (a A) Print(s string) { println(s) } // 型 I を引数にとる関数 func Output(i I) { i.Print("hello") } func main() { Output(A{}) }
具体的な例として、ioパッケージに Reader インターフェース が定義してあります。
以下の通り Read(p []byte) (n int, err error)
メソッドを持っていれば、この io.Reader
型を名乗ることができます。
type Reader interface { Read(p []byte) (n int, err error) }
bytes.Buffer
は Read(p []byte) (n int, err error)
を持っているので、io.Reader
型を名乗れる。
Read
関数を持っていれば、それ以外の関数を持っていても問題ない
package main import ( "bytes" "io" ) func Print(r io.Reader) { buff := make([]byte, 256) for { _, err := r.Read(buff) if err == io.EOF { break } println(string(buff)) } } func main() { var b bytes.Buffer b.WriteString("hello world") Print(&b) }
同様に *os.File
も Read
を持っているので、io.Reader
を名乗れる
package main import ( "io" "log" "os" ) func Print(r io.Reader) { buff := make([]byte, 256) for { _, err := r.Read(buff) if err == io.EOF { break } println(string(buff)) } } func main() { f, err := os.Open("file.txt") if err != nil { log.Fatal(err) } defer f.Close() Print(f) }
当然、自分で定義した構造体でも Read
関数さえ持っていれば、io.Reader
を名乗れます。
package main import ( "io" ) type A struct { } func (a A) Read(b []byte) (n int, err error) { return 0, io.EOF } func Print(r io.Reader) { buff := make([]byte, 256) for { _, err := r.Read(buff) if err == io.EOF { break } println(string(buff)) } } func main() { var a A Print(a) // 型アサーションのようにするとio.Readerの要件を満たしているかどうか判定できる _, hasReadMethod := interface{}(a).(io.Reader) println(hasReadMethod) // true }