Go1.24 New Features
Go1.24 が2月にリリースを控え、リリースノートのドラフトや Release Candidate が公開されています。この記事では前回の Go1.23 New Features に引き続き、Go1.24 の新機能の中から気になった機能を紹介していきます。
spec
Generics版のtype alias(generic type alias)がサポートされるようになります。type alias自体はGo1.9から存在し、主に型を別のパッケージに移行するリファクタリングのために利用されていましたが、Genericsでも同様の機能が必要となり今回追加されました。
type aliasを使うのは容易ですが、Genericsが導入されたGo1.18(2022年のwinter release)からgeneric type aliasが導入されるのに時間がかかった背景としては、静的解析に用いるパッケージgo/types
やコンパイラに対し大幅な変更が必要だったことがあります。
そのため、複数の段階・リリースに分けてgeneric type aliasを導入するためのステップを踏み、必要な準備がようやく整ったため、今回晴れて導入された形となっています。
go/types
等を使ったサードパーティのパッケージがgeneric type aliasに対応するための準備期間として、generic type aliasを無効にするGOEXPERIMENT=noaliastypeparams
が使えますが、Go1.25からは完全に有効になる予定です。
tools
go command
プロジェクトが依存するGo製ツールの管理をgo.mod
で行う機能が追加されます。Go1.23まではtool.go
等でblank importを行って管理するtipsが公式wikiで提案されていましたが、go.mod
のtool directiveで行えるようになります。
go.mod
へのtool directiveの追加は手動でも行うことができますが、go get
コマンドの-tool
フラグで行うこともできます。
go get -tool path/to/toolname
ツールのバージョンの管理はgo.mod
のrequire directiveで行われます。go get
時にバージョンを明示的に指定しない場合は最新バージョンのインポートパスが、指定した場合はそのバージョンのインポートパスがgo.mod
のrequire directiveに追加されます。
go.mod
で管理するツールは、インポートパスの最後の部分(上記のtoolname
の部分)を使ってgo tool
のサブコマンドのように実行できます。
go tool toolname
ツールのバイナリはビルドキャッシュ(go env GOCACHE
)に格納されるため、Go1.23以前に比べるとより多くの容量を消費することになります。
ツールの一括アップグレードは以下のコマンドで行えます。
go get -u tool
また、ビルドキャッシュだけでなく、go env GOBIN
に明示的にインストールする方法も提供されています。
go install tool
go.mod
で管理しているツール一覧を確認したい場合は単に
go tool
を実行すれば良いです。
認証が必要なリポジトリからpackageをimportする手段として、環境変数GOAUTH
が追加されました。記述方法についてはgo help goauth
で確認できます。
go build
時に、その時点でのメインモジュールのバージョンが、生成されるバイナリに含まれるようになりました。含みたくない場合は明示的にフラグ-buildvcs=false
を指定する必要があります。
GOCACHEPROG
ビルドキャッシュの保存・取得の管理はgoのプロセスがローカルのファイルシステムに対して直接行っています。
しかし、GitHub Actionsなどのステートレスなworkflowでは、キャッシュを別の場所に保存・取得するために圧縮・解凍をする必要がありますが、この処理がビルド本体よりも時間がかかることがしばしばあります。
そこで、ビルドキャッシュの管理をgoのプロセス自身ではなく、子プロセスを作成し処理を委譲させる仕組みが導入されました。これにより例えば、子プロセスを介してキャッシュを別のサーバーに送信することで、圧縮・解凍の過程をなくすことができるようになります。またそれだけでなく、キャッシュの管理方法をコントロールすることが可能となります。
新しく導入される環境変数GOCACHEPROG
に子プロセスを実行するコマンドを渡すことでこの仕組みを利用できます。
この課題感と解決策はtailscaleのbradfitzを中心に提案・議論され、子プロセスとして起動するキャッシュ管理プログラムのサンプル実装がすでに存在します。実装の仕方やGOCACHEPROG
の利用方法の参考になります。
ちなみにtailscaleではすでに稼働中で、うまく動作しているみたいです。
runtime
Swiss Tableをベースとした新しいmap
の実装や、小さいオブジェクトのより効率的なメモリ割り当て実装、ランタイム内部の排他処理の改善により、Go公式のベンチマーク全体で2~3%のCPU使用率減少を見込めます。
linker
リンカ実行時にBuild IDを元にした識別子を生成し、オブジェクトファイルや実行バイナリに含むようになります。Linux等のELFを出力するプラットフォームではGNU build ID(.note.gnu.build-idセクション)が、macOSではUUID(Mach-OのロードコマンドのLC_UUID)が含まれるようになります。
new packages
weak
Weak Pointerの機能を提供するパッケージが新規で追加されました。変数への通常の参照を強参照(Storong Reference)、その格納場所をPointerと呼ぶのに対し、弱参照(Weak Reference)の格納場所のことをWeak Pointerと呼びます。弱参照は、弱参照の元となった変数への強参照が必要なくなった後、弱参照が残っている場合でもGCされる可能性があります(強参照化を行う際にnilとなる)。GCの対象外としたい場合は、関数runtime.KeepAlive
を用いることで対象外にすることも可能です。一見使いにくそうに思えますが、キャッシュなど参照が残っていてもGCして欲しいシチュエーションで効果を発揮します。
ちなみに、Weak Pointerの実装はGo1.23にinternal packageとしてすでに含まれていました。というのも、Go1.23で導入されたunique packageを実現するために、グローバルなmapを保持しておく必要があるのですが、interningする対象が増えるに連れてmapが肥大化していく問題がありました。その解決策として、mapのvalueとしてWeak Pointerが導入・利用されていたのでした(内部実装やメソッド名はweak packageとして公開する際に一部変更がありました)。
crypto/mlkem
公開鍵暗号はその計算困難度により安全性を保ってきていましたが、量子コンピュータの発展により現在主流の方式では解かれてしまう可能性が出てきました。
そこで量子コンピュータを克服するための新しい方式(ポスト量子暗号)の研究開発・標準化も量子コンピュータ自体の研究開発と並行して行われてきています。とはいえ解かれるのはまだ可能性の話で、実際に解かれるのには猶予があるので、今すぐに実装は必要ないかと思われるかもしれません。
ところが、公開鍵暗号で暗号化された暗号文を今のうちに集めておき、将来量子コンピュータで解けるようになったときに一斉に解くHNDL(Harvest Now, Decrypt Later)攻撃というものがあります。これには今のうちから対策が必要で、実際に対策しはじめている会社やプロトコルが増えてきました。特に公開鍵暗号の応用の中でも、このHNDL攻撃への対策が必要なのが鍵交換です。
鍵交換に関して、特に参照事例が増えてきている標準化があります。NIST(National Institute of Standards and Technology)がリリースしたML-KEMです[1]。Go1.24では、このML-KEMが導入されます。
なお、NISTはML-KEMと同時にML-DSA、SLH-DSAという電子署名に関するアルゴリズムもリリースしましたが、HNDL攻撃で影響を受けず、優先度が高くないため、今回は実装されていません。
crypto/hkdf, crypto/pbkdf2, crypto/sha3
暗号化に関するパッケージの開発がgolang.org/x/crypto
と標準パッケージとで分散して実装されていましたが、golang.org/x/crypto
での開発を止め、標準パッケージに徐々に寄せていく動きがあります。Go1.24では3つのパッケージcrypto/hkdf, crypto/pbkdf2, crypto/sha3
が、golang.org/x/crypto
から標準パッケージに移行されます。
testing/synctest
時間の経過を伴うテストを実装したい場合、time.Sleep
やtime.Ticker
等で設定した時間が実際に経過するまで待つ必要がありました。Go1.24からは実験的サポートではありますが、新しく追加されるパッケージtesting/synctest
により、実時間の経過を待たずにテストを実行することができるようになります。
minor changes to packages
bytes, strings
byteスライス、stringスライス(bytes, strings)をイテレータで操作(走査)するメソッドがいくつか追加されます。
Lines
改行区切りでbytes, stringsを取得可能なイテレータ関数を返します。
SplitSeq
指定したseqで区切られたbytes, stringsを取得できるイテレータ関数を返します。
SlitAfterSeq
SplitSeqとほぼ同じですが、取得できるbytes, stringsのそれぞれの要素の末尾にseqがついています。
FieldsSeq, FieldsFuncSeq
空白(unicode.IsSpace
)で区切られたbytes, stringsが取得できるイテレータ関数を返します。
encoding.(Binary|Text)Appender
encoding.(Binary|Text)Marshaler
を満たす実装では、新しいスライスを生成します。また、それを別のバッファに入れる使い方をすることもしばしばあるため、新しく生成したスライスをすぐに破棄することも多いかなと思います。そこで、既存のスライスを引数で渡して、それに追加する形で再利用できるようにencoding.(Binary|Text)Appender
が追加されました。
encoding.(Binary|Text)Appender
を満たすように追加実装が加えられたtypeは以下になります。
encoding.BinaryAppender
hash.(Hash|Hash32|Hash64)
crypto/x509.OID
math/rand/v2.(ChaCha8|PCG)
net/netip.(Addr|AddrPort|AddrPrefix)
net/url.URL
time.Time
encoding.TextAppender
crypto/x509.OID
log/slog.(Level|LevelVar)
math/big.(Float|Int|Rat)
net.IP
net/netip.(Addr|AddrPort|AddrPrefix)
regexp.Regexp
time.Time
crypto/cipher
amd64やarm64環境において、cipher.Stream
をcipher.NewCTR
で初期化する際、aes.NewCipher
で初期化されたcipher.Block
を引数として渡す場合、数倍の処理速度向上が見込まれます。
crypto/rand
関数Read
はnil
を必ず返すようになりました。(以前までerror
を返していた状況ではrecover
不能な状態になる)
Reader
は、Linux6.11以降のvDSOの関数getrandom()
がサポートされている環境において、その関数を使用するようになりました。
暗号学的に安全なランダムな文字列を生成する関数Text
が追加されました。
crypto/subtle
アーキテクチャによって、データそれぞれで処理時間が決まる命令を持つものがあります。この挙動を利用してどのようなデータが処理されているかを推測するタイミング攻撃が行われる可能性がありました。Go1.24からはこれを回避するための新しい関数subtle.WithDataIndependentTiming
が追加されます。この関数に渡した関数内で実行する処理に関しては、処理時間を推測しにくくする処理が施されます。現在はarm64のDIT(Data Independent Timing)がサポートされた環境でのみ有効にできます。
crypto/tls
Go1.23ではTLS client側でECH(Encrypted Client Hello)がサポートされていましたが、TLS server側でもサポートされます。有効にするには、Config.EncryptedClientHelloKeys
を設定する必要があります。
TSL client, serverの鍵交換の仕組みとしてポスト量子暗号を用いたハイブリッド鍵交換X25519MLKEM768がデフォルトとして採用されます。また、どの鍵交換の仕組みを採用するかは、crypto/tls package内で完全に制御されるようになりました。
encoding/json
JSON Marshal時に利用する構造体タグomitzero
が追加されます。この構造体タグが設定されているフィールドの値がJSON Marshal時にゼロ値だった場合、省略されるようになります。ゼロ値の判定は、フィールドにメソッドIsZero() bool
があればそれを利用して行われ、なければspecに従って行われます。
omitempty
との違いは、omitempty
が指定したフィールドの値が空の場合に省略されるのに対し、omitzero
はゼロ値の場合に省略される点が違います。
特に、値を設定せずに初期化した構造体の挙動が違います。例えば、time.Time{}
はomitempty
では空ではないので省略されないのに対し、omitzero
では省略されます。
omitzero
とomitempty
の両方が設定されている場合は、どちらか片方でも満たす場合に省略されるようになります。
hash/maphash
引数として与えられたmaphash.Seed
と、比較可能な値から、ハッシュ値を生成する関数maphash.Comparable
が追加されます。この関数は、maphash.Seed
と比較可能な値の組み合わせが同じであれば、毎回同じハッシュ値を生成する性質があります。
また、maphash.Hash
に同様の性質を持つ値を書き込むWriteComparable
も追加されます。
log/slog
出力を常に破棄するexportedな変数DiscardHandler
が追加されます。
math/rand
非推奨になっていた関数Seed
がGo1.24からは機能しなくなります。
net
マルチパスTCP(MPTCP)がサポートされているLinux環境では、デフォルトで使用されるようになります。
net/http
HTTP/2の設定項目を表現できる構造体HTTP2Config
が追加され、構造体http.(Transport|Server)
がHTTP2Config
をフィールドとして持つようになります。
HTTPのプロトコルを表現できる構造体Protocols
が追加され、構造体http.(Transport|Server)
がProtocols
をフィールドとして持つようになります。
Protocols
のメソッドSetUnencryptedHTTP2
を呼ぶことで、暗号化を行わないHTTP/2(以下、非暗号化HTTP/2)で通信することも可能になります。
具体的には、Server.Protocols
に対してSetUnencryptedHTTP2(true)
を設定することで、HTTP/1と非暗号化HTTP/2のリクエストを同じポートで受けることが可能になります。また、Transport.Protocols
に対してSetUnencryptedHTTP2(true)
を設定することで、http://
でのリクエストの際に非暗号化HTTP/2を使用するようになります。
これらの変更は、上記のgolang.org/x/crypto
と同様にgolang.org/x/net/http2
から標準パッケージに移行する作業の一環となっています。
os
指定したディレクトリ配下でのみ操作可能な機能を提供する型os.Root
が追加されます。
os.Root
は新しく追加されるメソッドos.NewRoot
で初期化し、os.Root
が提供する各種メソッドによりディレクトリ操作を行います。この型を使うことで、意図せずディレクトリ外への不正なアクセスを防ぐことが可能となります。
プラットフォームごとに微妙に挙動が異なるので、詳しくはpackage documentをご覧ください。
runtime
関数GOROOT
は非推奨になります。非推奨になった背景としては、環境変数としてGOROOT
を設定していない場合、ビルド時に書き込まれた値を返すようになっていたのですが、ビルドされた環境とは別の環境で生成されたバイナリを使用する場合、その書き込まれた値が使用する環境とは関係のない値になっていたためです。
プログラム中から引き続きGOROOT
を参照したい場合は、go env GOROOT
を使用するよう推奨されています。
オブジェクトがGCによって解放される際に実行したい処理を設定する関数にruntime.SetFinalizer
がありますが、より使いやすく、安全に使用できる関数runtime.AddCleanup
が追加されました。Go1.24以降はruntime.SetFinalizer
の代わりにruntime.AddCleanup
を使うことが推奨されています(シグネチャが違うので移行するには工夫が必要)。
testing
testing(テスト、ベンチマーク、ファジング)用のcontext.Context
を生成するメソッドtesting.(T|B|F).Context
が追加されます。このcontext.Context
はテスト終了時かメソッドt.Cleanup
の実行時にキャンセルされるようになっています。
testing実行時にワーキングディレクトリを一時的に変更できるメソッドtesting.(T|B|F).Chdir
が追加されます。注意点として、testingを並列で実行している最中には利用できません(正確にはtesting.(B|F)
では利用はできるが、このメソッドはプロセス全体に影響を与えるので、予測不要な結果になる場合がある)。
ベンチマークで同じ処理を繰り返し実行して結果を安定させる場合、Go1.23まではfor range b.N {...}
のようにb.N
を使っていました。
Go1.24からは代わりに新しいメソッドtesting.B.Loop
が追加されます。これを使用してfor b.Loop() {...}
のように置き換えることができます。
置き換えることによるメリットとしては、大まかに次の2つがあります。
- ベンチマーク関数が1回のみ実行されること。これにより、セットアップやクリーンアップが何度も実行されなくなる。(
b.N
を使ったベンチマークではベンチマークにかかった処理時間が十分になるまで複数回ベンチマーク関数が実行される可能性がある) -
b.Loop
を使ったループ内の関数呼び出しは、コンパイラ最適化されないため、コンパイラ最適が意図せずベンチマーク結果に影響を与えるのを防ぐことができる。(呼び出された関数の処理内はコンパイラ最適化される可能性はある。あくまでも関数呼び出し部分のインライン展開等を防ぐのみにとどまる)
text/template
テンプレート内でrange-over-func
およびrange-over-int
が使用可能になります。
-
正確には鍵交換の1つの鍵カプセル化メカニズム(KEM: Key Excapsultion Mechanism) ↩︎
Discussion