Yuri’s Tech Note

技術系アウトプット

A Tour of Go vol.7(Interfaces/Type assertions)

Interfaces

・メソッドの型だけを定義した型
・オブジェクトの振る舞いを定義する

下記の例では Abserをインターフェースとする変数aに対し、Abserで定義しているメソッドAbs()を実装していない型の変数を代入しようとしてエラーになる。

// インターフェースAbserはメソッドAbsを振る舞いとして定義
type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser -> 1
    a = &v // a *Vertex implements Abser -> 2

    // In the following line, v is a Vertex (not *Vertex)
    // and does NOT implement Abser.
    a = v

    fmt.Println(a.Abs())
}

type MyFloat float64

// 1: MyFloatはメソッドabs()を持つ
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

// 2: *Vertex(ポインタ型)はメソッドabs()を持つ
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

インターフェースを実装する際はimplementsの表記は必要ない。

インターフェースのわかりやすい実装例
dev.classmethod.jp

nil

一旦離脱するがGoではnilは型を持つ

func main() {
    var i32 *int32
    fmt.Println(i32 == nil)  // true

    var arr []int
    fmt.Println(arr == nil)  // true

    fmt.Println(compare(i32, arr))  // false
}

int32型のnilと配列型のnilは頭皮比較false

基本的にnilは "==nil" がtrueになるが、インターフェースの時だけ型もnilでないと==nilはfalseとなる。

func main() {
    var arr []int
    fmt.Println(arr == nil)  // true
    fmt.Println(reflect.ValueOf(arr).IsNil())  // true
    fmt.Println(reflect.TypeOf(arr))  // []int

    var arr2 interface{} = arr
    fmt.Println(arr2 == nil)  // false
    fmt.Println(reflect.ValueOf(arr2).IsNil())  // true
    fmt.Println(reflect.TypeOf(arr2))  // []int

    var arr3 interface{}
    fmt.Println(arr3 == nil)  // true
    fmt.Println(reflect.TypeOf(arr3))  // <nil>
}

arr2はinterfaceとして扱っているため、型がnilでないので== nilはfalseとなる

isNilはあくまでBalueOfの下から値がnilだということを言いたいのか。
== nilは値に限らずnilをチェックしているのだろうが深堀はしない。

Type assertions

インターフェースはどんな型でも受け入れるので引数の型をインターフェース型にすればどんな型の値も受け取る関数をつくることができる。

しかし、この方法で受け渡された値は元の型がなにだったのかということを知ることができない。

この時に型アサーションを使えば動的に元の型がなんであるのかをチェックすることができる。

value, ok := <変数>.(<型>)

valueには型アサーション成功時に実際の値が格納され、2番目の変数okにはアサーションの成功否が格納される。

func main() {
    printIf(12)
    printIf("hello")
    printIf([]string{"cat", "dog"})
    printIf([2]string{"hello", "world"})
}

func printIf(src interface{}) {
    if value, ok := src.(int); ok {
        fmt.Printf("parameter is integer. [value: %d]\n", value)
        return
    }

    if value, ok := src.(string); ok {
        value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる
        fmt.Printf("parameter is string. [value: %s]\n", value)
        return
    }

    if value, ok := src.([]string); ok {
        value = append(value, "unknown") // 対象がsliceなのでAppendができる
        fmt.Printf("parameter is slice string. [value: %s]\n", value)
        return
    }

    fmt.Printf("parameter is unknown type. [valueType: %T]\n", src)
}

Type switches

アサーションと分岐を組み合わせた処理を手軽に記述することができる。

func printIf(src interface{}) {
    switch value := src.(type) {
    case int:
        fmt.Printf("parameter is integer. [value: %d]\n", value)
    case string:
        value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる
        fmt.Printf("parameter is string. [value: %s]\n", value)
    case []string:
        value = append(value, "<不明>") // 対象がsliceなのでAppendができる
        fmt.Printf("parameter is slice string. [value: %s]\n", value)
    default:
        fmt.Printf("parameter is unknown type. [valueType: %T]\n", src)
    }
}