golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) インターフェース interface{} の使い方

golang.png


Go言語のinterface{}の役割は以下の2つです。

  1. どんな型の値でも入れておける入れ物
    (TypeScript の any型 や C言語の void型 のようなもの)

  2. 構造体がメッソッド(関数)を持つことを保証するための型の定義





目次



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 interfacePrint(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.BufferRead(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.FileRead を持っているので、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
}