현재 3권의 신간들인 Go Optimizations 101, Go Details & Tips 101Go Generics 101이 출간되어 있습니다. Leanpub 서점에서 번들을 모두 구입하시는 방법이 비용 대비 효율이 가장 좋습니다.

Go에 대한 많은 정보들과 Go 101 책들의 최신 소식을 얻으시려면 Go 101 트위터 계정인 @Go100and1을 팔로잉 해주세요.

Some Undocumented Changes in Go 1.18 and 1.19

When a new Go version is released, the changes in this version are generally listed the release notes of the version. However, occasionally, some changes were missing in the release notes.

This article lists several missing changes in Go 1.18 and 1.19 release notes.

GoTv

We use GoTV to demonstrate the missing changes in this article. GoTV (Go Toolchain Version) is a tool used to manage and use multiple coexisting installations of official Go toolchain versions harmoniously and conveniently.

If you haven't, you may install GoTV by running

go install go101.org/gotv@latest

then run the following commands to install the latest releases of Go 1.17, Go 1.18 and Go 1.19 toolchains.

gotv 1.17. version
gotv 1.18. version
gotv 1.19. version

The first execution of the gotv command needs to pull the Go language project, so it might need several minutes to finish.

The definition of the terminology "slice of bytes" is clarified

Go specification states "a non-constant value x can be converted to type T if x is a string and T is a slice of bytes, and vice versa".

But what is a slice of bytes? There are two interpretations:

  1. any type whose underlying type is []byte.
  2. any slice type whose element type's underlying type is byte.

Before Go 1.18, the official standard Go compiler sometimes adopted the first interpretation, sometimes adopted the second interpretation. Since Go 1.18, the official standard Go compiler always adopts the second interpretation. And Go 1.19 formally admitted the 1.18+ implementation is correct.

For example, the official standard Go compiler 1.17 thinks the function f in the following code is valid but thinks the function g is invalid.

package main

type T byte

//go:noinline
func f(s string) []T {
	return []T(s)
}

//go:noinline
func g(x []T) string {
	return string(x) // error (line 12)
}

func main() {
	_ = f("Go")
	_ = g([]T{71, 111})
}

Since Go toolchain 1.18, both of the two functions compile okay. The followings are the outputs of the official standard Go compiler 1.17 and 1.18 for the above program.

$ gotv 1.17. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.17.13/bin/go run main.go
# command-line-arguments
./main.go:12:15: cannot use <node SPTR> (type *T) as type *byte in argument to runtime.slicebytetostring

$ gotv 1.18. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.18.7/bin/go main main.go

The official standard Go compiler 1.17 sometimes views []T as a "slice of bytes" (in the function f), sometimes doesn't (in the function g). If the type T byte type definition is changed into a type alias declaration type T = byte , then the function g is also viewed as valid by the official standard Go compiler 1.17.

The same situation happened for "slices of runes" (or "rune slice").

Note, up to now (Go 1.19), the reflect standard package has not been updated accordingly. For example, the following program prints two false, but should print two true instead.

package main

import "reflect"

type T byte

func main() {
	var s string
	var x []T
	var sType = reflect.TypeOf(s)
	var xType = reflect.TypeOf(x)
	println(sType.ConvertibleTo(xType))
	println(xType.ConvertibleTo(sType))
}

References:

A subtle detail in local constant declarations

What should the following program print?

package main

func main() {
	const (
		iota = iota
		Y
	)
	println(Y)
}

Some gophers think it should print 1, which is actually the answer of the official standard Go compiler 1.17.13-. However, since the official standard Go compiler 1.18, the answer is 0. The followings are the outputs with different compiler versions:

$ gotv 1.17. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.17.13/bin/go run main.go
1

$ gotv 1.18. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.18.7/bin/go run main.go
0

Which answer is correct?

The Go specification says:

Within a parenthesized const declaration list the expression list may be omitted from any but the first ConstSpec. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list.

The description states the 1.18+ answer is correct, because the above program is equivalent to

package main

func main() {
	const (
		iota = iota
		Y    = iota
	)
	println(Y)
}

The last iota is the local declared iota, which value is 0.

Go 1.19 confirmed it was a bug in 1.17.13- versions.

Ref: https://github.com/golang/go/issues/49157

A long-time type alias related bug was fixed

With the introduction of type alias in Go 1.8, a comparison bug was also introduced.

The following program should print false, but it prints true when using the official standard Go compiler from version 1.8 to 1.17.13.

package main

type T struct{}
type S = T

type A = struct{ T }
type B = struct{ S }

func main() {
	var a A
	var b B
	println(interface{}(b) == interface{}(a)) 
}

The type aliases A and B denote different types (with different field names), which is why the comparison should result false.

The bug was fixed in Go 1.18.

The followings are the outputs with different compiler versions:

$ gotv 1.17. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.17.13/bin/go run main.go
true

$ gotv 1.18. run main.go
[Run]: $HOME/.cache/gotv/tag_go1.18.7/bin/go run main.go
false

Ref: https://github.com/golang/go/issues/24721

The "named type" terminology came back to Go specification

With the introduction of type alias in Go 1.8, the "named type" terminology was removed from Go specification. A new terminology "defined type" was introduced to fill in the gaps caused by the removal of "named type". However, this made many old Go tutorials and articles become obsolete and brought several other drawbacks.

With the introduction of custom generics, the "named type" terminology came back since Go 1.18, which is helpful to clearly explain many conversion and assignment rules in Go.

Now, a named type may be

Note, a type alias of an unnamed type is not a named type.

Goexit signals may cancel already happened (recoverable) panics

Should the following program crash for panicking or exit normally?

package main

import "runtime"

func main() {
	c := make(chan struct{})
	go func() {
		defer close(c)
		defer runtime.Goexit()
		panic("bye")
	}()
	<-c
}

Before Go 1.19, there was not a clear answer. Some gophers think it should crash, for the "bye" panic is never recovered. But in the implementation of the official standard Go runtime, a Goexit signal will cancel already happened (recoverable) panics in the current goroutine. So, with the official standard compiler, the above program will exit normally. The behavior was decided as correct in Go 1.19.

In my personal opinion, Goexit signals should not cancel panics, so the above program should crash instead. But I respect the final decision of the Go core team.

The final decision makes runtime.Goexit calls act as super recover calls. For example, the following two goroutine functions behave the same.

func goroutine1() {
	defer runtime.Goexit()
	panic(1)
}

func goroutine2() {
	defer func() {
		recover()
	}()
	panic(1)
}

The official Go documentation explains the panic/recover mechanism so simply that there are still several other details which don't get covered, which is why the Go 101 book includes a special article to explain the panic/recover mechanism in detail.

More small compiler and runtime changes

There are also some undocumented small compiler and runtime optimizations made in recent Go toolchain versions. Some of the small optimizations are mentioned in the Go Optimizations 101 book.


Index↡

The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.

주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.

Go 101의 저자인 Tapir는 2016년 7월부터 Go 101 시리즈 책들을 집필하고 go101.org 웹사이트를 유지 관리하고 있습니다. 새로운 콘텐츠는 책과 웹사이트에 수시로 추가될 예정입니다. Tapir는 인디 게임 개발자이기도 합니다. Tapir의 게임을 플레이하여 Go 101을 지원할 수도 있습니다. (안드로이드와 아이폰/아이패드용):
  • Color Infection (★★★★★), 140개 이상의 단계로 이루어진 물리 기반의 캐주얼 퍼즐 게임
  • Rectangle Pushers (★★★★★), 2가지 모드와 104개 이상의 단계로 이루어진 캐주얼 퍼즐 게임
  • Let's Play With Particles, 세가지 미니 게임이 있는 캐주얼 액션 게임
페이팔을 통한 개인 기부도 환영합니다.

색인: