0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

7.8 インタフェースとnilについて の考察

Posted at

基底型へのポインタとベース値へのポインタについて

インタフェースは「ベースとなる型(基底型)へのポインタ」と「ベースとなる値へのポインタ」の組で実装されています とある。これをunsafe.Pointerを使って具体的に見てみる。

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type eface struct {            // 空インタフェース(interface{})の内部表現に対応
    typ  unsafe.Pointer        // 実際は *runtime._type だがここではポインタで代用
    data unsafe.Pointer
}

func dumpEmptyInterface(x interface{}) {
    e := *(*eface)(unsafe.Pointer(&x)) // x(=interface{}) の中身を生で読む
    fmt.Printf("eface.typ  = %p (dynamic type = %v)\n", e.typ, reflect.TypeOf(x))
    fmt.Printf("eface.data = %p\n", e.data)

    // 参考:動的値がポインタ型なら、そのポインタ値も安全に確認できる
    v := reflect.ValueOf(x)
    if v.Kind() == reflect.Ptr {
        fmt.Printf("dynamic pointer value = 0x%x (isNil=%v)\n", v.Pointer(), v.IsNil())
    }
}

func main() {
    var s *string
    fmt.Println(s == nil) // true

    var i interface{}
    fmt.Println(i == nil) // true

    fmt.Println("dumpEmptyInterface 1回目")
    dumpEmptyInterface(i)

    i = s
    fmt.Println(i == nil) // false

    fmt.Println("dumpEmptyInterface 2回目")
    dumpEmptyInterface(i)
}

これを実行すると以下のようになる

true
true
dumpEmptyInterface 1回目
eface.typ  = 0x0 (dynamic type = <nil>)
eface.data = 0x0
false
dumpEmptyInterface 2回目
eface.typ  = 0x10414ae40 (dynamic type = *string)
eface.data = 0x0
dynamic pointer value = 0x0 (isNil=true)

dumpEmptyInterface()の実行により、基底型のポインタには*stringが入っていることがわかる。
ちなみに、型ポインタも値ポインタもnilの場合、nilインタフェースと呼ぶ

動的型と静的型について

インタフェースが内部に持つ「型ポインタ」は動的型(中身の具体型)へのポインタであって、静的型へのポインタではない。
そのため

var i io.Reader

と宣言した場合、i == nilはtrueになるが、これは動的な型がnilであるため。

静的型

コンパイル時に決まる"その変数の型"。宣言や代入で固定され、コンパイラが代入可否・呼べるメソッドをチェックする根拠。var i io.Readerの静的型は常にio.Reader.

動的型

インタフェース値に格納されている実際の具体型。実行中に代入で変わる。
reflect.TypeOf(i)fmt.Printf("%T", i)で判別可能

具象型のインスタンスが nil でもメソッド呼び出し自体は可能

これはそのまま

package main

import "fmt"

type Counter struct{ n int }

// ★ nil レシーバでも安全に動くよう、自分で nil を判定
func (c *Counter) SafeInc() {
	if c == nil {
		fmt.Println("nil receiver; skip")
		return
	}
	c.n++
}

func main() {
	var c *Counter = nil
	c.SafeInc() // OK: "nil receiver; skip"
}

実行すると nil receiver; skip が出力される

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?