Go1.20 New Features
Go1.20 が2023年2月2日にリリースされ、そのリリースノートが公開されています。この記事ではその中から気になったものを抜粋し、いくつかの機能に関しては使用例も載せていきます。
それでは見ていきましょう!
spec
slice から array への変更が可能になりました。
Go1.17 では slice から array の pointer に変換できる言語仕様が追加されましたが、slice から array に変換するには一度 pointer を経由しなければいけませんでした。Go1.20 からは直接 slice から array に変換できるようになります。
/*
slice を要素数4の array に変換する
*/
x := []int{1, 2, 3, 4}
// Go1.20 より前
a1 := *(*[4]int)(x)
// Go1.20 以降
a2 := [4]int(x)
comparable
で型制約された Generics を、「interface 型」や「interface 型を持つ spec comparable な複合型(struct 型など)」といった strictly comparable ではない型を型引数に取ってインスタンス化することができるようになりました。ただし、strictly comparable でないことにより、ランタイム時に panic になる可能性が出てきたことに注意が必要です。
以下のコードのコンパイルが通るようになります。
package main
import "io"
func F[T comparable]() {}
type I[T comparable] interface{ G(T) }
func main() {
F[any]()
F[io.Reader]()
F[struct{ any }]()
F[[1]any]()
F[I[any]]()
}
spec comparable や strictly comparable などの用語についての説明は Nobishii さんの記事が詳しいです。
go version
Go 製の Windows DLL や実行権限のない Linux 用バイナリからも go version -m
で情報を取得できるようになりました。
go command
ビルド済の標準パッケージを $GOROOT/pkg に含まないことにより、Go ツールチェーンの圧縮ファイルのサイズが前リリースバージョンと比較して約2/3になりました。なおビルド済の標準パッケージは、3rdパーティパッケージと同様にビルド時にキャッシュされます。
アーキテクチャごとにサブアーキテクチャを環境変数で指定することで、サブアーキテクチャをビルドタグとして認識できるようになりました。
例えば、ARMv6 と v7 でビルドを分けたい場合は、ビルドする際に環境変数 GOARM=6
、GOARM=7
をそれぞれ指定すれば、ビルドタグとして arm.6
、arm.7
が使えるようになり、ビルド対象となるファイルを分けることができます。
go のサブコマンドに -C フラグが追加され、コマンドを実行するディレクトリを変更できるようになりました。
go generate に -skip フラグが追加され、パターンにマッチする //go:generate
ディレクティブをスキップできるようになりました。
go test に -skip フラグが追加され、パターンにマッチするテスト関連の関数をスキップできるようになりました。
go build 時に -cover フラグをつけることで、統合テスト用のコードカバレッジを計測できるようになりました(Go1.20 ではプレビューサポートとなっているので、環境変数 GOEXPERIMENT=coverageredesign を設定する必要があります)。使い方については、以下のページに詳しく記載されているのでご覧ください。
cgo
C ツールチェーンのない環境では、環境変数 CGO_ENABLED
がデフォルトで無効になったため、明示的に有効にしない場合は Go ベースの標準パッケージを使用するようになりました。C ツールチェーンがない環境とは、具体的には、環境変数 CGO_ENABLED
と CC
が明示的に設定されていない、かつ gcc や clang などの C コンパイラが PATH
で見つからない環境です。ちなみに cgo を使用している標準パッケージは net, os/user, plugin です。
macOS では完全に cgo を使わないように再実装されたので、CGO_ENABLED
を有効にしても Go ベースの標準パッケージを使用するようになります。他の OS では、CGO_ENABLED
を有効にした場合は今まで通り cgo を使用した標準パッケージが使用できます(特に理由がない限りは Go ベースのほうを使ったほうが良さそうです)。
macOS 用の race detector は cgo を使わない Go ベースのものに再実装されたため、Xcode なしで race detector を実行できるようになりました。
vet
ループ変数が関数に多重にネストされている場合でもgo vet で検知するようになりました。
time layout が ISO8601 の形式 2006-01-02(yyyy-mm-dd) ではなく、2006-02-01(yyyy-dd-mm) になっていた場合、go vet で検知するようになりました。
compiler
Profile-Guided Optimization(PGO) がプレビューサポートされました。Go バイナリにビルドする際の最適化処理をビルド時だけで完結するのではなく、ランタイム時のプロファイル情報を収集して次回以降のビルドに活かす、コンパイラ最適化の手法の一つです。
内部的には、pprof を使用しており、Go 1.20 時点では CPU プロファイルがサポートされます。これにより、頻繁に呼び出す関数やメソッドを積極的にインライン化するようになります。
今後は CPU 以外のプロファイルもサポートされていく予定です。
PGO をオンにするには go build, go install, go run などビルド系のコマンドで -pgo
フラグを有効にする必要があります。
-pgo=auto
と指定することで、main package にある default.pgo を検索して、存在すれば使用します。-pgo=<filepath>
と指定することで、ファイルパスを明示することもできます。
詳しくはユーザーガイドが公開されているので、ご覧ください。
次の型定義のように循環して interface を定義すると、ビルドエラーになるようになりました。
なお後方互換性が崩れる変更となるため、保ちたい場合はビルド時に -d=interfacecycles
を指定してください。
もし悪影響がない場合、Go 1.22 で言語仕様として正式にビルドエラーになります。
type I interface { m() interface { I } }
Go1.18 で Generics が導入されビルド時間が長くなっていましたが、Go1.20 では Go1.19 に比べ9%ほど高速化され、Go1.17 並の速度に戻りました。
linker
Linux では、ベースとなる C ライブラリが glibc と musl libc とが存在しており、ビルド時に参照する Dynamic Interpreter(ld.so等)が異なるため、Go ツールチェーンはそれぞれ用意されていました。Go 1.20 からはビルド時に Dynamic Interpreter を自動検知するするようになったため、1つの Go ツールチェーンを用意するだけで良くなりました。この変更で影響があるのは、例えば musl ベースの Linux ディストリビューションである Apline Linux を Docker Base Image にし、Go をインストールして独自の Docker Image 使っている方です(恐らくソースコードをダウンロードしてきて make.bash
を実行してインストールされていると思うのですが、https://go.dev/dl/go${VERSION}.linux-${GOARCH}.tar.gz
がそのまま使えるようになります)。
macOS 上で DNS による名前解決を行う際に、Go ビルトインの機能ではなく macOS ネイティブの Resolver を使うようになりました。
これにより、macOS 以外の環境で macOS 向けにクロスコンパイルを行った Go バイナリでも、macOS 上で動作させる際に、macOS ネイティブの Resolver の挙動と差異なく動作させることができます。
bootstrap
Go1.20 のデフォルトのブートストラップツールチェーンとして Go1.17.13 が使用されるようになりました。
package
arena
arena package が新しく実験的にサポートされました。
Garbage Collection(GC) の影響を受けずにメモリを確保できる機構です。GC に使用していた CPU リソースを他の処理に活かせるようになるため、確保したメモリを長時間解放せず、GC される可能性が明らかにないと分かっている場合に使用すると良いかもしれません。
具体的な使い方に関しては、後日アドカレで記事にする予定なのでお楽しみに!
bytes, strings
bytes package に関数 CutPrefix, CutSuffix
が追加されました。
バイト列 A の先頭や末尾からバイト列 B を取り除いたバイト列 C を返せるようになります。動作は TrimPrefix, TrimSuffix
と同じですが、CutPrefix, CutSuffix
は第2戻り値として取り除けた場合は true, 取り除けなかった場合は false を返す点で異なります。
さらに、strings package にも同様の機能が追加されました。
bytes
bytes package に関数 Clone
が追加されました。
バイト列をクローンして返せるようになります。
context
context package に関数 WithCancelCause, Cause
が追加されました。
WithCancelCause
が返す context.Context
がすでにキャンセル済みである場合エラーを、キャンセルされていない場合 nil
を返すことで、キャンセル済みかどうかを判断できるようになります。
errors, fmt
複数の error を同時に Wrap できるようになりました。
方法としては以下の2種類があります。
-
fmt.Errorf
の format string に Wrap したい error の数だけ%w
を用いる。 - 新しく追加された関数
errors.Join
を用いる。
err1 := errors.New("err1")
err2 := errors.New("err2")
// 複数の %w を用いる場合
err3 := fmt.Errorf("err: %w, %w", err1, err2)
// errors.Join を用いる場合
err4 := errors.Join(err1, err2)
同時に Wrap する際の戻り値は、interface{ Unwrap() []error }
を実装することで errors.Is
や errors.As
できちんと捕捉できるようになります。ただし、errors.Unwrap
で Unwrap する際は interface{ Unwrap() error }
を実装していないことになるので nil
が返ってくることになります。
fmt.Println(errors.As(err3, &err1), errors.As(err3, &err2))
fmt.Println(errors.As(err4, &err1), errors.As(err4, &err2))
fmt.Println(errors.Unwrap(err3))
fmt.Println(errors.Unwrap(err4))
// Output:
// true true
// true true
// <nil>
// <nil>
fmt
fmt package に関数 FormatString
が追加されました。
関数 fmt.Printf
などで用いられるフォーマット文字列を、インターフェース fmt.State
を用いてプログラマブルに生成できるようになります。
go/types
go/types package に関数 Satisfies
が追加されました。
型が指定した型制約を満たしているか確認する際に使うことができます。
なお、型が指定した interface を実装しているかの確認には使用できません。既存の関数 Implements
を使いましょう。
io
io package に構造体 OffsetWriter
が追加されました。
math/rand
math/rand package の関数 Read, Seed
は非推奨になりました。
以前の math/rand package の package global な乱数生成器は、シードを定めないまま乱数を生成するとシードに1を設定したものとして初期化されていました。
Go1.20からは、math/rand package をインポートした時点でランダムにシード値が定められるようになりました。ただ、上述の通り非推奨になったため、乱数を生成する際は個別に乱数生成器を初期化して使用しましょう。
Go1.20 より前
Go1.20 以降
net/http
net/http package に構造体 ResponseController
とそれを初期化する関数 NewResponseController
が追加されました。NewResponseController
の引数に渡す ResponseWriter
が以下のメソッドを実装している場合、そのメソッドを透過的に呼ぶことができます。
Flush()
FlushError() error // alternative Flush returning an error
Hijack() (net.Conn, *bufio.ReadWriter, error)
SetReadDeadline(deadline time.Time) error
SetWriteDeadline(deadline time.Time) error
特に SetReadDeadline
や SetWriteDeadline
によって、リクエストごとに読み書きのデッドラインを設定できるようになります。
net/http package の構造体 Server
にフィールド DisableGeneralOptionsHandler
が追加されました。リクエスト "OPTIONS *" が来た時に、このフィールドが true
の場合は対応する Handler が処理を行い、false
の場合は Handler で処理を行わずに「Content-Length:0 の 200 OK」を返すようになります。
net/http package の HTTP サーバーでは、ボディを含む HEAD リクエストも受けつけるようになりました。
Cookie 名の前後に空白が含まれていても無視されずに、トリムされた値として受け入れられるようになりました。
例えば Cookie が "name =value" の場合、Cookie 名は "name" となります。
os/exec
os/exec package の構造体 Cmd
にフィールド Cancel
と WaitDelay
が追加されました。
reflect
reflect package の構造体 Value
にメソッド Comparable
と Equal
が追加されました。
2つの値が ==
で比較したときに true
だった場合は Equal
は同じく true
を返し、false
の場合も同様に false
を返します(実際のところ条件はもう少し複雑なので、詳しくは GoDoc をご覧ください)。
Comparable
はそもそも値が ==
で比較可能かを示します。
reflect package の構造体 Value
にメソッド Grow
が追加されました。
Value.Kind() == Kind.Slice
の場合に限り、必要な分だけ cap を増やした後、引数の n
だけ len を増やします。
Value.Kind() != Kind.Slice
の場合や、n
が負や大きすぎる場合は panic を起こします。
reflect package の構造体 Value
にメソッド SetZero
が追加されました。
Value.Kind()
に対応するゼロ値を付与します。
regexp
正規表現パーサーがメモリを大量に消費する場合、Go1.19.2 と Go1.18.7 で入ったパッチにより syntax.ErrInternalError
を返していましたが、Go1.20 からはエラー内容をより明確にするために syntax.ErrLarge
を返すようになりました。
sync
sync package の構造体 Map
にメソッド Swap, CompareAndSwap, CompareAndDelete
が追加され、より厳密なアトミック処理が行えるようになりました。
time
time package に time layout に使える定数 DateTime, DateOnly, TimeOnly
が追加されました。
DateTime = "2006-01-02 15:04:05"
DateOnly = "2006-01-02"
TimeOnly = "15:04:05"
time package の構造体 Time
にメソッド Compare
が追加されました。
「以前」や「以降」を判定したい場合、今までは Before/After
と Equal
の2つを組み合わせて呼んでいましたが、Compare
の戻り値を比較するだけで良くなりました。
/*
現在時刻 now が有効期限 expiredAt 以降である場合 true
*/
expiredAt := time.Date(2009, 11, 10, 19, 00, 0, 0, time.UTC)
now := time.Now()
// Go1.20より前
fmt.Println(now.Equal(expiredAt) || now.After(expiredAt))
// Go1.20以降
fmt.Println(now.Compare(expiredAt) >= 0)
strconv
strconv package の関数 ParseXXX
に []byte
を string
に変換して渡す際に、内部で明示的に string
をコピーすることによって、コンパイラのエスケープ解析機能を利用してヒープへのメモリアロケーションを行わないようにできるようになったため、パフォーマンスが改善されました。
unsafe
unsafe package に関数 SliceData, String, StringData
が追加されました。
これにより、より直感的に slice と string を unsafe に扱えるようになります。
/*
string を []byte にコピーせずに変換する
*/
s := "hello, world!"
// Go1.20 より前
b1 := *(*[]byte)(unsafe.Pointer(&s))
// Go1.20 以降
b2 := unsafe.Slice(unsafe.StringData(s), len(s))
Discussion