技術メモ

技術メモ

ラフなメモ

インターフェース値のnilを扱うということ

インターフェース型

Golangのインターフェース型は、具象型がインターフェースのインスタンスとして見なされるために必要なメソッドの集まりを定義します。例えば標準パッケージのioに含まれる以下のReaderやWriterはインターフェース型です。

package io

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

インターフェース値

インターフェース値はインターフェース型の値です。インターフェース値は以下のようなデータ構造をとります。

f:id:tutuz:20191022153530p:plain

research!rsc: Go Data Structures: Interfaces より引用

概念としてインターフェース値はtypeとvalueから構成されます。インターフェースのゼロ値はtypeとvalueがともにnilです。

インターフェース値がnilであるということは?

typeがnilでないがvaluenilであるインターフェース値と、typeもvaluenilインターフェースであるインターフェース値は異なります。

公式のドキュメント https://golang.org/doc/faq#nil_error にその内容があります。

type myError struct {
    message string
}

func (e *myError) Error() string {
    if e == nil {
        return "myError: <nil>"
    }
    return "myError: " + e.message
}

func returnsError() error {
    var p *myError = nil
    if isDebug {
        p = &myError{"myError occured"}
    }
    return p // Will always return a non-nil error.
}

上記の実装は常に error インターフェースは nil になりません。

reflect で 型情報を出力すると明らかです。

package main

import (
    "fmt"
    "reflect"
)

var isDebug = false

type myError struct {
    message string
}

func (e *myError) Error() string {
    if e == nil {
        return "myError: <nil>"
    }
    return "myError: " + e.message
}

func returnsError() error {
    var p *myError
    if isDebug {
        p = &myError{message: "myError occured"}
    }
    return p // Will always return a non-nil error.
}

func main() {
    err := returnsError()
    if err != nil {
        fmt.Println("err is NOT <nil>")
    } else {
        fmt.Println("err is <nil>")
    }

    // err is NOT <nil>
    fmt.Println("----------------------------")
    fmt.Println("is nil:", err == nil)
    fmt.Println("Type:", reflect.TypeOf(err))
    fmt.Println("Value:", reflect.ValueOf(err))

    // err is <nil>
    fmt.Println("----------------------------")
    var nilErr error
    fmt.Println("is nil:", nilErr == nil)
    fmt.Println("Type:", reflect.TypeOf(nilErr))
    fmt.Println("Value:", reflect.ValueOf(nilErr))
}

https://play.golang.org/p/21Cj7MKOiLV

err is NOT <nil>
----------------------------
is nil: false
Type: *main.myError
Value: myError: <nil>
----------------------------
is nil: true
Type: <nil>
Value: <invalid reflect.Value>

ということで正しい実装例は以下になります。

BadGood
func returnsError() error {
    var p *myError = nil
    if isDebug {
        p = &myError{"myError occured"}
    }
    return p // Will always return a non-nil error.
}
func returnsError() error {
    if isDebug {
        return &myError{"myError occured"}
    }
    return nil
}

参考