はじめに
本記事は, Goクイズ Advent Calender 2020の12/24用に書かれました。
昨日は, @binzumeさんの「Goクイズ: int = float64」でした。
Index expressionsに関する問題を出そうと思ったのですが, 以前Twitterで出題した問題と似ている気がしたので, 急遽Type assertionsに関する問題に取り替えました。
では, 本題に入ります。
問題
以下のコードを実行すると, どのように出力されるでしょうか?
Playground
package main
import (
"fmt"
)
type I interface {
Hello()
}
type T struct{}
func (T) Hello() {
fmt.Println("Hello!")
}
func main() {
var i I = nil
v, _ := i.(T)
v.Hello()
}
- compile error
- i.(T)の代入部分でrun-time panic
- v.Hello()の実行部分でrun-time panic
- Hello!
解答と解説
まず, 型アサーション(Type assersions)について確認します。 For an expression インタフェース型の式 ということは, A type assertion used in an assignment or initialization of the special form yields an additional untyped boolean value. The value of ok is true if the assertion holds. Otherwise it is false and the value of v is the zero value for type T. No run-time panic occurs in this case. すなわち, この形式で代入および初期化をした場合, run-time panicは発生せず, 1つ目の返り値に型 ここで, "この形式"というのは, 2つ目の返り値まで受けとる形式を指すと私は考えています。なぜなら, 2つ目の形式を受け取らない場合, 下記のコードになり, run-time panicとなるためです。 2020/12/24追記: 何はともあれ, この場合型 結果として, 対策として, 2つ目の返り値を受け取った場合は, 下記の通り分岐を入れると良いと思います。 2021/01/14追記: ここまでお読みいただきありがとうございました。解答と解説
回答は, 4. Hello!
です。
TL;DR
解説
x
of interface type and a type T
, the primary expression x.(T)
asserts that x is not nil and that the value stored in x
is of type T
. The notation x.(T) is called a type assertion.x
と型T
について下記の通り書かれています。
x.(T)
という表記法を型アサーションと呼ぶx.(T)
はx
がnilではなくx
に格納されている値が型T
のものであることを保証するx.(T)
の型アサーションが実行される場合, x
がnilでないことが保証される訳です。しかし, 本問ではi.(T)
におけるiはnilです。これはどういうことなのでしょうか?その答えは後述されている文章にあります。
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)
ok
の値はアサーションが保持されていればtrue
であるfalse
であり, vの値は型T
のゼロ値であるT
の変数が格納されます。package main
import (
"fmt"
)
type I interface {
Hello()
}
type T struct{}
func (T) Hello() {
fmt.Println("Hello!")
}
func main() {
var i I = nil
var v = i.(T) // panic: interface conversion: main.I is nil, not main.T
v.Hello()
}
これを見た時に, 型アサーション(Type assertions)の冒頭に書かれていた, 「x.(T)
はx
がnilではないことを保証する」という定義と矛盾するように感じました。
良く考えたら, 2つ目の返り値にfalseが格納されており, 型アサーションは保持されていません。そのため, 冒頭に書かれていた定義は適用されないので何の問題もありませんでした。T
という情報は保持されるので, 当然v.Hello()
も実行できます。Hello!
が出力されます。
おわりに
package main
import (
"fmt"
"log"
"os"
)
type I interface {
Hello()
}
type T struct{}
func (T) Hello() {
fmt.Println("Hello!")
}
func main() {
var i I = nil
v, ok := i.(T)
if !ok {
log.Println("invalid assertion")
os.Exit(1)
}
v.Hello()
}
この形式は, nil
から""
(empty string)に変換する際にも使えますね。Nullを含むレコードがあるときに使えそう。package main
import (
"fmt"
)
func main() {
var n interface{} = nil
if _, ok := n.(string); !ok {
n = ""
}
fmt.Println("n: ", n.(string))
fmt.Println("n==nil: ", n == nil)
}
明日は, @tenntennさんのラスボス問題があるようです。楽しみですね~。