Go言語のバージョン1.22がリリースされました。
どのような仕様変更があるのか、主要どころをざっとみていきたいと思います。
ループ変数がイテレーションごとに初期化
以下のようなforループの処理があったとします。
func main() {
for i := 0; i < 5; i++ {
f := func() { fmt.Println("This is number", i) }
go loopFunc(f)
}
}
func loopFunc(f func()) {
f()
}
以前のバージョンまではループ変数(上記コード例でいうi)はイテレーション間で使いまわされていました。
つまり、各イテレーションで発火されたゴルーチンのloopFuncが実行される頃には、すでに変数iのアドレスには最後のイテレーションの数値4が入っており、「This is number 4」という文字列が5回表示されてしまう仕様でした。
Go1.22ではこの仕様が変更され、ループ変数がイテレーションごとに異なるスコープを持つようになりました。
つまり上記例ではgo loopFunc(f)の5回の実行は、それぞれiのイテレーションごとの異なる値(0~4)を参照するようになりました。
そのため1.22では上記コードは「This is number 0(/ 1 / 2/ 3/ 4)」をそれぞれ1回ずつ表示するようになります。
※なおGo1.21ではこの機能が実験的に導入されており、GOEXPERIMENT=loopvarの接頭辞を明示的に指定した場合に限り、この仕様が適用されるようになっています。
詳しくは私の過去の記事を参照ください。
1.21で実験的導入にとどまっていたものが、1.22で本格的に使用に加わったということですね。
整数型にrangeを使える
ループ処理で使うrangeを、int型に対しても使えるようになりました。
for i := range 10 {
fmt.Println("The number is", i)
}
例えば上記コード例だと、iは0から9の間で1ずつ加算され、10回ループします。
パフォーマンスの改善
ランタイムのメモリ最適化により、CPUのパフォーマンスが1~3%ほど改善されました。またメモリのオーバーヘッドも1%ほど減少しています。
標準ライブラリの変更いろいろ
slices.Concat
slicesパッケージにConcat関数が追加されました。スライス同士を結合させることができます。
sl1 := []string{"Michael", "John", "Paul"}
sl2 := []string{"Robby", "Kevin", "Tom"}
sl3 := slices.Concat(sl1, sl2)
fmt.Println(sl3)
// output:
// [Michael John Paul Robby Kevin Tom]
※slicesパッケージはGo1.21で標準パッケージに追加された、スライスを操作する関数をたくさん提供してくれる便利なものです。過去の記事でこのパッケージのソースコードを覗いてみたりしています。↓
math/rand/v2パッケージ
擬似乱数生成の新しいパッケージです。
従来のmath/randパッケージに比べ、全体的にアルゴリズムがより高速になっています。
標準パッケージでv2と付くのは、地味にこのパッケージがGo史上初だったりします。
http.ServeMuxがより使いやすく
ルーティングの設定ができるhtt.ServerMuxですが、より設定が直感的にできるようになりました。
mux := http.NewServeMux()
mux.Handle("GET /hoge/", someHandler)
このようにルーティングを設定する際のパターンを
メソッド名+スペース+パス
の文字列で渡すことで、メソッドごとのルーティングをよりシンプルに、分かりやすく表現できるようになりました。
type Handler struct{}
func (Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Request path: %s, method: %s\n", r.URL.String(), r.Method)
if r.Method == "POST" {
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintf(w, "Request body: %s\n", string(body))
}
}
func main() {
mux := http.NewServeMux()
h := &Handler{}
mux.Handle("GET /hoge/", h) // メソッド+スペース+パスでルーティング設定
mux.Handle("POST /hoge/", h) // メソッド+スペース+パスでルーティング設定
_ = http.ListenAndServe(":8080", mux)
}
// 1. GETでリクエスト
// - リクエスト: curl 'http://localhost:8080/hoge/'
// - レスポンス: Request path: /hoge/, method: GET
// 2. POSTでリクエスト
// - リクエスト: curl -X POST 'http://localhost:8080/hoge/' -d '{"name":"John"}'
// - レスポンス: Request path: /hoge/, method: POST
// Request body: {"name":"John"}
きちんとメソッド別のルーティング処理がされていますね。
(※なおメソッドをパターンで明記しない場合は、GETメソッドのルーティングになります。)
また、パスをワイルドカードで捕捉できるようになりました。
mux := http.NewServeMux()
mux.Handle("GET /customer/{name}", h)
例えばこのような形であれば、パスの{id}部分がどんな値であれこのルーティングがマッチし、{id}の実際のパスの値はRequest.PathValueメソッドから取得できます。
type Handler struct{}
func (Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
fmt.Fprintf(w, "Name is %s\n", name)
}
func main() {
mux := http.NewServeMux()
h := &Handler{}
mux.Handle("GET /customer/{name}", h) // {name}部分をワイルドカードとして捕捉
_ = http.ListenAndServe(":8080", mux)
}
// リクエスト: curl 'http://localhost:8080/customer/jackson'
// レスポンス: Name is jackson
ワイルドカード部分のパスの取得がきちんとできていることが分かります。
バージョンアップするたびにどんどん使いやすくなっていきますね。痒い所に手が届くような改良が毎回されていて、Goユーザとして嬉しい限りです。
特にサーバ設定関連の標準ライブラリはシンプルかつ機能が充実していて、外部のフレームワークなしでも十分に実用性があると感じます。
以上、Go1.22のざっくりまとめでした。
コメント