基底型へのポインタとベース値へのポインタについて
インタフェースは「ベースとなる型(基底型)へのポインタ」と「ベースとなる値へのポインタ」の組で実装されています とある。これを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 が出力される