フューチャー技術ブログ

Go 1.22リリース連載始まります & ループの変化とTinyGo 0.31

The Gopher character is based on the Go mascot designed by Renée French

Future Tech Blog恒例のGoリリース連載が始まります。

Date Title Author
1/29 インデックス & ループの変化とtinygo 1.31 渋川
1/30 slicesのマイナーアップデート 辻大志郎
1/31 archive/tar, archive/zip, bufio, io 真野隼記
2/1 encoding, encoding/json 棚井龍之介
2/2 HTTPルーティング強化 武田大輝
2/5 vet, log/slog, testing/slogtest 真野隼記
2/6 30種類のプログラミング言語で、ループ処理を書いてみた 棚井龍之介
2/14 net, net/http, net/netip 真野隼記

Go 1.22のトピックとしては以下のようなものがあります。だいぶ安定版になってきたからか、言語もライブラリもこつぶなものが多くなってきたかな、という印象です。

  • ループ変数の挙動の変化
  • ツール系
    • GO111MODULE=offオプションの廃止
    • トレースツールのUI改善
    • net/httpのServeMuxでパス変数が扱えるように
    • go vet強化
    • コンパイラでGCが1-3%高速化。PGOで2-14%改善
    • リンカーの生成するバイナリがよりデバッガフレンドリーに
  • ライブラリ
    • math/rand/v2追加
    • database/sql.NUllの追加
    • net/httpのルーターがパスパラメータをとれるように
    • その他

個人的に注目しているHTTP/3やQUICへの対応は、準標準ライブラリのgolang.org/x/net/internal/quicの中で進行中。将来的にはinternalが外れたgolang.org/x/net/quicができて、そののちに標準ライブラリ化される予定のようですが、まだ時間かかりそうですね。

https://github.com/golang/net/tree/master/internal/quic

2/1追記
database/sqlのNullの追加は以下のブログエントリーが詳しいです。コミットした本人によるブログ記事なので日本だけでなく海外含めてもこれよりも詳しい説明は存在しないでしょう。
methaneのブログ: sql.Null[T] をGo 1.22に追加しました

ループの変化(1)

Go 1.22は言語の変化としてはループ変数の扱いが変わったり、固定回数のループが描きやすくなったり、今後のバージョンで入る予定のrange over functionという機能が GOEXPERIMENT=rangefunc という環境変数のスイッチで有効になったりします。まずはループ変数の扱いの変化から説明します。

Goのループ変数は単なる参照でした。で、goroutineの起動には時間がかかります。次のようなコードを1.21までのGoで実行するとgoroutineが起動するころには呼び出し元のループは終わっていました。iは単なる参照で、iの最終の値は9なので、だいたい9が出力されます。

package main

import (
"fmt"
"sync"
)

func main() {
values := make([]int, 10)
var wg sync.WaitGroup
wg.Add(len(values))
for i := range values {
// ループごとにgoroutineを起動
go func() {
// goroutineの起動は単なるforループよりは重い処理なので
// ここが実行されるころにはもうループは終わっている
fmt.Println(i)
wg.Done()
}()
}
wg.Wait()
}

これに対処するために、わざわざ新しいメモリ領域が確保されるようにする必要がありました。主に2つの方法がありました。しかし、

// ループの中で変数を定義して代入
// この変数はクロージャ(別の関数)から参照されるため、エスケープ解析により
// ヒープ領域にメモリが確保される
for i := range values {
i := i
:
}

// goroutine作成時に明示的に渡す
// goroutine側のスタックメモリにコピーされる
for i := range values {
go func(i int) {
:
}(i)
}

Go 1.22からは、ループ変数が内部のクロージャなどから参照されたりした場合はi := iを自動で差し込むようになり、呼び出したタイミングでのループ変数の値が中でも利用できます。

Goでgoroutineをループであつかう場合のよくある落とし穴が塞がれました。

ループの変化(2)

range over intというのが入りました。今まで、10回繰り返したい!みたいなループはC言語からの伝統のループを書く必要がありました。

// C言語からの伝統のループ
for i := 0; i < 10; i++ {
fmt.Println(i)
}

これがこのように書けるようなります。

// 1.22からの新しいループ
for i := range 10 {
fmt.Println(i)
}

ループの変化(3)

Go 1.23以降に入る予定だが、1.22で実験的に実装されているものがrange over functionです。

古の書のデザインパターンを読んだことがある人もいるかもしれません。その中で「イテレーターパターン」というものがありました。繰り返し処理を自前で実装する方法を表現したものです。Goの標準ライブラリの中にもいくつかあります。イテレーターパターンはその実装方法から、内部と外部と2種類の分類が知られています

// 内部イテレータ
// ループ条件の判断などは Walk()関数が行い、ユーザーはループの中身だけ実装する
err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
// ここでファイルごとの繰り返し処理
return nil
})

// 外部イテレータ
// ループの条件判断はイテレータオブジェクトのNext()メソッドなどで行う
// Next()はboolを返したり、値を返すがEOFのような番兵を返すことで有効なデータがあるか
// 判断する
rows := db.Query("select name, email from users")
for rows.Next() {
// ここで行ごとの繰り返し処理
}

だいたいの言語にはforループがあり、うまく外部イテレータを言語が定めるプロトコル通りに実装することで言語標準の文法の中で使えるような言語がいくつもあります。C++やJavaScriptのfor ofループや、Pythonの__iter__メソッドなどです。range over functionも、この仲間です。

使い方とかはすでに詳しくわかりやすく解説してくれているブログ記事があったりするのでそちらを見てもらえればよいかな、と思います。

これらがリリースされると、そのうち、次のように書けるようになるかと思います。まだExperimentalで変更もありえるので詳しくは説明しません。

// メソッド名は適当です
for path, info := range filepath.WalkOver(rootpath) {
// ここでファイルごとの繰り返し処理
}

// メソッド名は適当です
for row := range db.QueryOver("select name, email from users") {
// ここで行ごとの繰り返し処理
}

TinyGo 0.31

Go本体のリリースしかあまり見てなかったのですが、以下のtakasagoさんの投稿を見て、TinyGoもリリースを予定していることを知りました。

ここ数年のGoのリリースと、それらのバージョンにあわせたTinyGoのバージョンを表にしたものが次の通りです。READMEの説明を見ると、昔は「完全ではない」という表現もあったりしたのですが、おおむね、2-3週間のラグで対応するバージョンが出ているようです。

リリース日 対応するTinyGoバージョン(リリース日)
Go 1.21 2023/8/8 0.29 (2023/8/26)
Go 1.20 2023/2/1 0.27 (2023/2/12)
Go 1.19 2022/8/2 0.24 (2022/7/1、ただしベータ扱い), 0.25? (2022/8/3)
Go 1.18 2022/3/15 0.23 (2022/4/29、ただし全機能ではない)
Go 1.17 2021/8/16 0.20 (2021/9/22)

せっかくなので、Go 1.22サポートを目指している開発版のTinyGoをインストールしてみます。手順はTinyGoのウェブサイトのBuild from sourceのページにあります。

$ git clone --recursive https://github.com/tinygo-org/tinygo.git
$ cd tinygo
$ git checkout dev
$ git submodule update --init
// macなので。他の環境のやり方は上記ウェブサイト参照
$ brew install llvm
$ go install

さっそく実行してみようとすると、Go 1.22がないぞ、とのエラー。Go 1.22 rc 1をインストールして以下のrange over intのコードを動かしてみます。もう最新の言語サーバーのgoplsはrange over intを正しく解釈してくれるのですね。

package main

import "fmt"

func main() {
for i := range 10 {
fmt.Println(i)
}
}

以下のように実行すると、Go 1.22のコードをコンパイルして実行できました。小さいバイナリができてWASMでの利用もしやすいし、今後はちょくちょく触ってみようと思います。

$ tinygo run main.go
0
1
2
3
: