1
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?

More than 3 years have passed since last update.

Goクイズ: Type assertions

Last updated at Posted at 2020-12-23

はじめに

本記事は, 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()
}

  1. compile error
  2. i.(T)の代入部分でrun-time panic
  3. v.Hello()の実行部分でrun-time panic
  4. Hello!

解答と解説

解答と解説
回答は, `4. Hello!`です。

TL;DR

  • 特別な形式の代入か初期化に用いられる型Tへのアサーションは, untyped bool値を追加で生成する
  • 型アサーションが保持されていない場合, 1つ目の返り値は型Tのゼロ値, 2つ目の返り値はuntyped boolのfalseとなる
  • この場合, run-time panicは発生しない

解説

まず, 型アサーション(Type assersions)について確認します。

For an expression 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です。これはどういうことなのでしょうか?その答えは後述されている文章にあります。

A type assertion used in an assignment or initialization of the special form

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)

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.

  • これらの, 特別な形式の代入か初期化に用いられる型アサーションは型なしのブール値を追加で生成する
  • okの値はアサーションが保持されていればtrueである
  • それ以外の場合はfalseであり, vの値は型Tのゼロ値である
  • この場合, run-time panicは発生しない

すなわち, この形式で代入および初期化をした場合, run-time panicは発生せず, 1つ目の返り値に型Tの変数が格納されます。

ここで, "この形式"というのは, 2つ目の返り値まで受けとる形式を指すと私は考えています。なぜなら, 2つ目の形式を受け取らない場合, 下記のコードになり, run-time panicとなるためです。

Playground

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ではないことを保証する」という定義と矛盾するように感じました。

2020/12/24追記:
良く考えたら, 2つ目の返り値にfalseが格納されており, 型アサーションは保持されていません。そのため, 冒頭に書かれていた定義は適用されないので何の問題もありませんでした。

何はともあれ, この場合型Tという情報は保持されるので, 当然v.Hello()も実行できます。

結果として, Hello!が出力されます。

おわりに

対策として, 2つ目の返り値を受け取った場合は, 下記の通り分岐を入れると良いと思います。

Playground

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()
}

2021/01/14追記:
この形式は, nilから""(empty string)に変換する際にも使えますね。Nullを含むレコードがあるときに使えそう。

Playground

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さんのラスボス問題があるようです。楽しみですね~。

1
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
1
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?