🆕

Go1.24 New Features

2025/01/21に公開

Go1.24 が2月にリリースを控え、リリースノートのドラフトや Release Candidate が公開されています。この記事では前回の Go1.23 New Features に引き続き、Go1.24 の新機能の中から気になった機能を紹介していきます。

https://tip.golang.org/doc/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からは完全に有効になる予定です。

https://github.com/golang/go/issues/46477
https://go.dev/blog/alias-names

tools

go command

プロジェクトが依存するGo製ツールの管理をgo.modで行う機能が追加されます。Go1.23まではtool.go等でblank importを行って管理するtipsが公式wikiで提案されていましたが、go.modtool 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で確認できます。

https://github.com/golang/go/issues/26232


go build時に、その時点でのメインモジュールのバージョンが、生成されるバイナリに含まれるようになりました。含みたくない場合は明示的にフラグ-buildvcs=falseを指定する必要があります。

GOCACHEPROG

ビルドキャッシュの保存・取得の管理はgoのプロセスがローカルのファイルシステムに対して直接行っています。
しかし、GitHub Actionsなどのステートレスなworkflowでは、キャッシュを別の場所に保存・取得するために圧縮・解凍をする必要がありますが、この処理がビルド本体よりも時間がかかることがしばしばあります。
そこで、ビルドキャッシュの管理をgoのプロセス自身ではなく、子プロセスを作成し処理を委譲させる仕組みが導入されました。これにより例えば、子プロセスを介してキャッシュを別のサーバーに送信することで、圧縮・解凍の過程をなくすことができるようになります。またそれだけでなく、キャッシュの管理方法をコントロールすることが可能となります。
新しく導入される環境変数GOCACHEPROGに子プロセスを実行するコマンドを渡すことでこの仕組みを利用できます。

https://github.com/golang/go/issues/59719
https://pkg.go.dev/cmd/go/internal/cacheprog@master

この課題感と解決策はtailscalebradfitzを中心に提案・議論され、子プロセスとして起動するキャッシュ管理プログラムのサンプル実装がすでに存在します。実装の仕方やGOCACHEPROGの利用方法の参考になります。

https://github.com/bradfitz/go-tool-cache

ちなみにtailscaleではすでに稼働中で、うまく動作しているみたいです。

https://github.com/dominikh/go-tools/issues/1458

runtime

Swiss Tableをベースとした新しいmapの実装や、小さいオブジェクトのより効率的なメモリ割り当て実装、ランタイム内部の排他処理の改善により、Go公式のベンチマーク全体で2~3%のCPU使用率減少を見込めます。

https://go.dev/issue/54766
https://go.dev/cl/614795
https://go.dev/issue/68578

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して欲しいシチュエーションで効果を発揮します。

https://github.com/golang/go/issues/67552
https://pkg.go.dev/weak@master
https://victoriametrics.com/blog/go-weak-pointer/

ちなみに、Weak Pointerの実装はGo1.23にinternal packageとしてすでに含まれていました。というのも、Go1.23で導入されたunique packageを実現するために、グローバルなmapを保持しておく必要があるのですが、interningする対象が増えるに連れてmapが肥大化していく問題がありました。その解決策として、mapのvalueとしてWeak Pointerが導入・利用されていたのでした(内部実装やメソッド名はweak packageとして公開する際に一部変更がありました)。

https://github.com/golang/go/issues/62483
https://victoriametrics.com/blog/go-unique-package-intern-string/

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攻撃で影響を受けず、優先度が高くないため、今回は実装されていません。

https://github.com/golang/go/issues/70122
https://pkg.go.dev/crypto/mlkem@master
https://zenn.dev/uji/articles/go-crypto-post-quantum-support

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から標準パッケージに移行されます。

https://github.com/golang/go/issues/65269
https://github.com/golang/go/issues/61477
https://github.com/golang/go/issues/69488
https://github.com/golang/go/issues/69982

testing/synctest

時間の経過を伴うテストを実装したい場合、time.Sleeptime.Ticker等で設定した時間が実際に経過するまで待つ必要がありました。Go1.24からは実験的サポートではありますが、新しく追加されるパッケージtesting/synctestにより、実時間の経過を待たずにテストを実行することができるようになります。

https://github.com/golang/go/issues/67434
https://github.com/golang/go/blob/master/src/testing/synctest/synctest.go

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

https://github.com/golang/go/issues/62384

crypto/cipher

amd64やarm64環境において、cipher.Streamcipher.NewCTRで初期化する際、aes.NewCipherで初期化されたcipher.Blockを引数として渡す場合、数倍の処理速度向上が見込まれます。

crypto/rand

関数Readnilを必ず返すようになりました。(以前までerrorを返していた状況ではrecover不能な状態になる)
https://github.com/golang/go/issues/66821


Readerは、Linux6.11以降のvDSOの関数getrandom()がサポートされている環境において、その関数を使用するようになりました。

https://github.com/golang/go/issues/69577
https://www.phoronix.com/news/Linux-6.11-Lands-getrandom-vDSO


暗号学的に安全なランダムな文字列を生成する関数Textが追加されました。

https://github.com/golang/go/issues/67057

crypto/subtle

アーキテクチャによって、データそれぞれで処理時間が決まる命令を持つものがあります。この挙動を利用してどのようなデータが処理されているかを推測するタイミング攻撃が行われる可能性がありました。Go1.24からはこれを回避するための新しい関数subtle.WithDataIndependentTimingが追加されます。この関数に渡した関数内で実行する処理に関しては、処理時間を推測しにくくする処理が施されます。現在はarm64のDIT(Data Independent Timing)がサポートされた環境でのみ有効にできます。

https://github.com/golang/go/issues/49702
https://pkg.go.dev/crypto/subtle@master#WithDataIndependentTiming
https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Enable-DIT-for-constant-time-cryptographic-operations

crypto/tls

Go1.23ではTLS client側でECH(Encrypted Client Hello)がサポートされていましたが、TLS server側でもサポートされます。有効にするには、Config.EncryptedClientHelloKeysを設定する必要があります。

https://github.com/golang/go/issues/68500
https://blog.cloudflare.com/ja-jp/announcing-encrypted-client-hello/#echnoshi-zu-mi
https://defo.ie/ech-check.php


TSL client, serverの鍵交換の仕組みとしてポスト量子暗号を用いたハイブリッド鍵交換X25519MLKEM768がデフォルトとして採用されます。また、どの鍵交換の仕組みを採用するかは、crypto/tls package内で完全に制御されるようになりました。

https://github.com/golang/go/issues/69393
https://go-review.googlesource.com/c/go/+/630775

encoding/json

JSON Marshal時に利用する構造体タグomitzeroが追加されます。この構造体タグが設定されているフィールドの値がJSON Marshal時にゼロ値だった場合、省略されるようになります。ゼロ値の判定は、フィールドにメソッドIsZero() boolがあればそれを利用して行われ、なければspecに従って行われます。

omitemptyとの違いは、omitemptyが指定したフィールドの値が空の場合に省略されるのに対し、omitzeroはゼロ値の場合に省略される点が違います。
特に、値を設定せずに初期化した構造体の挙動が違います。例えば、time.Time{}omitemptyでは空ではないので省略されないのに対し、omitzeroでは省略されます。

omitzeroomitemptyの両方が設定されている場合は、どちらか片方でも満たす場合に省略されるようになります。

https://github.com/golang/go/issues/45669
https://github.com/golang/go/blob/go1.24rc2/src/encoding/json/encode.go#L1187-L1219
https://github.com/golang/go/blob/go1.24rc2/src/encoding/json/encode.go#L318-L330
https://github.com/golang/go/blob/master/src/encoding/json/encode.go#L715-L718

hash/maphash

引数として与えられたmaphash.Seedと、比較可能な値から、ハッシュ値を生成する関数maphash.Comparableが追加されます。この関数は、maphash.Seedと比較可能な値の組み合わせが同じであれば、毎回同じハッシュ値を生成する性質があります。
また、maphash.Hashに同様の性質を持つ値を書き込むWriteComparableも追加されます。

https://github.com/golang/go/issues/54670

log/slog

出力を常に破棄するexportedな変数DiscardHandlerが追加されます。

https://github.com/golang/go/issues/62005

math/rand

非推奨になっていた関数SeedがGo1.24からは機能しなくなります。

net

マルチパスTCP(MPTCP)がサポートされているLinux環境では、デフォルトで使用されるようになります。

net/http

HTTP/2の設定項目を表現できる構造体HTTP2Configが追加され、構造体http.(Transport|Server)HTTP2Configをフィールドとして持つようになります。
https://github.com/golang/go/issues/67813
https://pkg.go.dev/net/http@master#HTTP2Config

HTTPのプロトコルを表現できる構造体Protocolsが追加され、構造体http.(Transport|Server)Protocolsをフィールドとして持つようになります。
https://github.com/golang/go/issues/67814
https://pkg.go.dev/net/http@master#Protocols

ProtocolsのメソッドSetUnencryptedHTTP2を呼ぶことで、暗号化を行わないHTTP/2(以下、非暗号化HTTP/2)で通信することも可能になります。
具体的には、Server.Protocolsに対してSetUnencryptedHTTP2(true)を設定することで、HTTP/1と非暗号化HTTP/2のリクエストを同じポートで受けることが可能になります。また、Transport.Protocolsに対してSetUnencryptedHTTP2(true)を設定することで、http://でのリクエストの際に非暗号化HTTP/2を使用するようになります。
https://github.com/golang/go/issues/67816
https://pkg.go.dev/net/http@master#Protocols.UnencryptedHTTP2

これらの変更は、上記のgolang.org/x/cryptoと同様にgolang.org/x/net/http2から標準パッケージに移行する作業の一環となっています。

https://github.com/golang/go/issues/67810

os

指定したディレクトリ配下でのみ操作可能な機能を提供する型os.Rootが追加されます。
os.Rootは新しく追加されるメソッドos.NewRootで初期化し、os.Rootが提供する各種メソッドによりディレクトリ操作を行います。この型を使うことで、意図せずディレクトリ外への不正なアクセスを防ぐことが可能となります。
プラットフォームごとに微妙に挙動が異なるので、詳しくはpackage documentをご覧ください。
https://github.com/golang/go/issues/67002
https://pkg.go.dev/os@master#Root

runtime

関数GOROOTは非推奨になります。非推奨になった背景としては、環境変数としてGOROOTを設定していない場合、ビルド時に書き込まれた値を返すようになっていたのですが、ビルドされた環境とは別の環境で生成されたバイナリを使用する場合、その書き込まれた値が使用する環境とは関係のない値になっていたためです。
プログラム中から引き続きGOROOTを参照したい場合は、go env GOROOTを使用するよう推奨されています。

https://github.com/golang/go/issues/51473


オブジェクトがGCによって解放される際に実行したい処理を設定する関数にruntime.SetFinalizerがありますが、より使いやすく、安全に使用できる関数runtime.AddCleanupが追加されました。Go1.24以降はruntime.SetFinalizerの代わりにruntime.AddCleanupを使うことが推奨されています(シグネチャが違うので移行するには工夫が必要)。
https://pkg.go.dev/runtime@master#AddCleanup
https://github.com/golang/go/issues/67535

testing

testing(テスト、ベンチマーク、ファジング)用のcontext.Contextを生成するメソッドtesting.(T|B|F).Contextが追加されます。このcontext.Contextはテスト終了時かメソッドt.Cleanupの実行時にキャンセルされるようになっています。

https://github.com/golang/go/issues/36532


testing実行時にワーキングディレクトリを一時的に変更できるメソッドtesting.(T|B|F).Chdirが追加されます。注意点として、testingを並列で実行している最中には利用できません(正確にはtesting.(B|F)では利用はできるが、このメソッドはプロセス全体に影響を与えるので、予測不要な結果になる場合がある)。

https://github.com/golang/go/issues/62516


ベンチマークで同じ処理を繰り返し実行して結果を安定させる場合、Go1.23まではfor range b.N {...}のようにb.Nを使っていました。
Go1.24からは代わりに新しいメソッドtesting.B.Loopが追加されます。これを使用してfor b.Loop() {...}のように置き換えることができます。
置き換えることによるメリットとしては、大まかに次の2つがあります。

  • ベンチマーク関数が1回のみ実行されること。これにより、セットアップやクリーンアップが何度も実行されなくなる。(b.Nを使ったベンチマークではベンチマークにかかった処理時間が十分になるまで複数回ベンチマーク関数が実行される可能性がある)
  • b.Loopを使ったループ内の関数呼び出しは、コンパイラ最適化されないため、コンパイラ最適が意図せずベンチマーク結果に影響を与えるのを防ぐことができる。(呼び出された関数の処理内はコンパイラ最適化される可能性はある。あくまでも関数呼び出し部分のインライン展開等を防ぐのみにとどまる)

https://github.com/golang/go/issues/61515
https://pkg.go.dev/testing@master#B.Loop

text/template

テンプレート内でrange-over-funcおよびrange-over-intが使用可能になります。

https://github.com/golang/go/issues/66107

脚注
  1. 正確には鍵交換の1つの鍵カプセル化メカニズム(KEM: Key Excapsultion Mechanism) ↩︎

Discussion