Go Quiz: Type assertions

Introduction

This article was written for 12/24 of Go Quiz Advent Calender 2020.

Yesterday was @ binzume's "Go quiz: int = float64".

I wanted to ask a question about Index expressions, but I felt it was similar to Question on Twitter before, so I hurriedly replaced it with a question about Type assertions.

Now, let's get down to the main subject.

problem

What will the output look like when I run the following code? 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. Run-time panic in the substitution part of i. (T)
  3. v. Run-time panic in the execution part of Hello ()
  4. Hello!

Answers and explanations

Answers and explanations
The answer is `4. Hello!`.

TL;DR --Assertions to type T used for special forms of assignment or initialization generate an additional untyped bool value. --If no type assertion is preserved, the first return value will be a zero value of type T and the second return value will be false for untyped bool. --In this case, ** run-time panic does not occur **

Commentary

First, let's look at Type assersions (https://golang.org/ref/spec#Type_assertions).

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.

The interface type expressions x and type T are written as follows.

--The notation x. (T) is called a type assertion. --x. (T) guarantees that x is not nil and the value stored in x is of type T

This means that when a type assertion of x. (T) is performed, it is guaranteed that x is not nil. However, in this question, i in i. (T) is nil. What does this mean? The answer can be found in the text below.

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.

--The type assertions used for these special forms of assignment or initialization generate additional untyped Boolean values. --The value of ok is true if the assertion is retained --Otherwise it is false and the value of v is a zero value of type T. --In this case, ** run-time panic does not occur **

That is, when assigning and initializing in this format, run-time panic does not occur and a variable of type T is stored in the first return value.

Here, I think that "this format" refers to the format that accepts up to the second return value. This is because if you don't receive the second form, you'll end up with the code below, which is a 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()
}

~~ When I saw this, the definition "x. (T)guarantees that x is not nil "was written at the beginning of Type assertions. I felt it was inconsistent with. ~~

2020/12/24 postscript: If you think about it, the second return value contains false and doesn't hold any type assertions. Therefore, the definition written at the beginning does not apply, so there was no problem.

Anyway, in this case the information of type T is retained, so of course you can also executev.Hello ().

As a result, Hello! Is output.

in conclusion

As a countermeasure, if you receive the second return value, you should insert a branch as shown below.

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 postscript: This format can also be used when converting from nil to""(empty string). It can be used when there is a record containing 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)
}

Thank you for reading. Tomorrow, there seems to be @ tenntenn's last boss problem. I'm looking forward to it.

Recommended Posts

Go Quiz: Type assertions
About the basic type of Go