30
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

グロービスAdvent Calendar 2018

Day 20

Goのnilだけどnilじゃないちょっとだけnilな値

Posted at

突然ですが、以下のコードが何を意味しているかわかりますか?

var _ I = (*T)(nil)

答え

型チェックです。

次のようなコードが存在するとして、TがインターフェイスIを満たすことを宣言しています。

type I interface {}
type T struct {}

このチェックが行われるのは実行時ではなくコンパイル時です。また、割り当て先がブランク識別子_であるため、バイトコードにはこのコードに対応する部分は出力されません。

Effective Go - The Go Programming Language

それはわかったけど、(*T)(nil)ってキモくない? というか何してるの?

型の変換です。普通であればfloat32(1)のように書けますが、前に*がつく場合はカッコで囲む必要があります。*はポインタを現すために使うので、その場合と区別するために必要だとか。

Go's Declaration Syntax - The Go Blog

つまり、次の2つのコードは同じことをしています。

var a = (*T)(nil)
var b *T

後者を使用して冒頭のコードを書き換えるなら次のようになります。

var b *T
var _ I = b

ここで、anilから生成され、bはデフォルト値で初期化されていますので、両者共に直感的にはnilだと思われるのですが、実はこれらは同時にインターフェイス値でもあります。

nilが型を持っている?

次のコードと実行結果を眺めてみてください。

package main

import (
	"fmt"
)

type T struct {}

func main() {
    var b *T
    fmt.Println(b == nil) 
}
true

bnilなので当然の結果ですね。

次にreflectを使って型を調べてみます。

package main

import (
	"fmt"
	"reflect"
)

type T struct {}

func main() {
    var b *T
    fmt.Println(reflect.TypeOf(b))
}
*main.T

どうやらbを持っているようです。

こうなると、bはただのnilではなくインターフェイス値であると考えるのが自然ですね。

インターフェイス値は動的な型と値を持つ

簡単な例として、空のインターフェイスIを定義して、インターフェイス値iTのインスタンスへのポインタを代入してみます。

package main

import (
	"fmt"
	"reflect"
)

type I interface {}
type T struct {}

func main() {
    var i I
    fmt.Println(i == nil)
    fmt.Println(reflect.TypeOf(i))

    i = &T{}
    fmt.Println(i == nil)
    fmt.Println(reflect.TypeOf(i))

    i = nil
    fmt.Println(i == nil)
    fmt.Println(reflect.TypeOf(i))
}
true
<nil>
false
*main.T
true
<nil>

インターフェイス値iの型と値は変化し、最後は再びnilを代入され、元の状態に戻りました。

インターフェイス値は別の型に変換できる

代入ではなく、明示的な変換を行ってみます。

Uという構造体を追加しました。

package main

import (
	"fmt"
	"reflect"
)

type T struct {}
type U struct {}

func main() {
    var b *T
    var c = (*U)(b)
    fmt.Println(c == nil) 
    fmt.Println(reflect.TypeOf(c))
}
true
*main.U

bUに変換したものは依然としてnilを値として持ちますが、型はUになります。

ただし、interface{}への変換を除く

では次にbinterface{}に変換してみます。

package main

import (
	"fmt"
)

type T struct {}

func main() {
    var b *T
    var d = interface{}(b)
    fmt.Println(d == nil)
    fmt.Println(reflect.TypeOf(d))
}
false
*main.T

おや…? もう少し検証してみましょう。再び構造体UbUに変換したcを追加します。
そしてbcをそれぞれinterface{}に変換してみます。

package main

import (
	"fmt"
	"reflect"
)

type T struct {}
type U struct {}

func main() {
    var b *T
    var c = (*U)(b)
    var d = interface{}(b)
    var e = interface{}(c)
    fmt.Println(reflect.TypeOf(b))
    fmt.Println(reflect.TypeOf(c))
    fmt.Println(reflect.TypeOf(d))
    fmt.Println(reflect.TypeOf(e))
}
*main.T
*main.U
*main.T
*main.U

TそしてUinterface{}に変換した場合、型が変化していないことに注目してください。

これが、先ほどnilとの比較がfalseになった原因です。

まとめ

  • Goにはインターフェイス値というものがあり、を持っている。

  • インターフェイス値はそれぞれ動的である。

  • nil以外のものからinterface{}に変換されたインターフェイス値は、としてはnilを持っていたとしても、依然としてnil以外のを持つため、nilとの比較がfalseになる。

もしこの記事に興味を持っていただけたなら、Go Data Structures: Interfaces あるいは「プログラミング言語 Go 7章 インターフェイス」に「インターフェイス値」「インターフェイスの動的な型」「インターフェイスの動的な値」「型記述子」などの情報がまとまっています。

30
10
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
30
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?