プログラミング
Go 1.27 インタラクティブツアー
Go 1.27 Interactive Tour (victoriametrics.com)
要約
Go 1.27のリリースノートを基にしたハンズオン形式のガイドです。本記事では、メソッド宣言で独自の型パラメータを宣言できるようになったジェネリックメソッド、構造体リテラルで埋め込みフィールドを直接指定できるようになったフィールドセレクタ、そして関数型推論の一般化といった主要な新機能について、実行可能な例を交えて解説します。さらに、メモリ割り当ての高速化、トレースバックでのゴルーチンラベル表示、ゴルーチンリークプロファイル、そしてポスト量子署名(ML-DSA)の実装といった、その他の重要な変更点も紹介しています。
全文翻訳
Blog / Go 1.27 インタラクティブツアー
Go 1.27 がもうすぐリリースされるため、新機能について事前に把握しておくのに良い時期です。公式リリースノートはやや簡潔なので、ここでは変更点とその新しい動作がどのように機能するかを示す、実行可能な例を交えた実践的なバージョンを提供します。
まず、感謝を述べたいと思います。インタラクティブなGoツアーは Anton Zhiyanov 氏によって開始され、彼は Go 1.22 から Go 1.26 までの各リリースについてツアーを作成しました。彼はこれを終了することにしたため、私たちがそのバトンを引き継ぎます。彼の以前のツアーはすべて、今でも読む価値があります。
Go 1.22 インタラクティブツアー
Go 1.23 インタラクティブツアー
Go 1.24 インタラクティブツアー
Go 1.25 インタラクティブツアー
Go 1.26 インタラクティブツアー
Anton さん、ありがとうございます。
新しい機能の詳細に入る前に、文脈を整理しましょう。
この記事は、BSD-3-Clause ライセンスの下で提供されている公式リリースノートと Go のソースコードに基づいています。これは網羅的なリストではありません。完全なリストについては、公式リリースノートを参照してください。
リンクは、各機能のドキュメント(𝗗)、提案(𝗣)、最も関連性の高いコミット(𝗖𝗟)、および作成者(𝗔)を指しています。動機、使用法、および実装の詳細については、それらをチェックしてください。作成者(𝗔)は、機能に貢献した人々(実装、テストの作成、または以前の実験から卒業した機能の場合は元の設計)であり、必ずしも単一の主要な作成者ではありません。
エラー処理は、例を短く保つためにしばしば省略されています。本番環境ではこれを行わないでください ツ
ジェネリックメソッド
これはリリースにおける見出しです。メソッド宣言は、レシーバーの型パラメータとは独立して、独自の型パラメータを宣言できるようになりました。Go 1.27 より前は、トップレベル関数のみがジェネリック可能だったため、型のジェネリック操作はメソッドではなくパッケージレベルの関数として存在する必要がありました。
ジェネリックコンテナがあり、要素の型を変更できる Map 操作を行いたいとします。
```go
type Box[T any] struct{ v T } // メソッドは独自の型パラメータ U (Go 1.27 で新規) を宣言します。
func (b Box[T]) Map[U any](f func(T) U) Box[U] {
return Box[U]{v: f(b.v)}
}
```
これで Map は Box のメソッドとなり、int ボックスを string ボックスに変換できます。
```go
func main() {
b := Box[int]{v: 21}
doubled := b.Map(func(n int) int { return n * 2 })
label := doubled.Map(func(n int) string { return fmt.Sprintf("value=%d", n) })
fmt.Println(label.v)
}
```
出力:
```
value=42
```
1 つ重要な制限があります。インターフェースは依然として型パラメータ化されたメソッドを宣言できず、ジェネリックメソッドを使用してインターフェースを満たすことはできません。インターフェースにジェネリックメソッドを配置すると、コンパイラが停止します。
```go
type Mapper interface {
Map[U any](f func(int) U) any // インターフェースはジェネリックメソッドを宣言できません
}
// interface method must have no type parameters
```
𝗗 Composite literals
𝗣 9859
𝗖𝗟 1a8f9d8, 9f7e98d, 30bfe53, e2c1885
𝗔 Robert Griesemer, Cherry Mui
構造体リテラルのキーは、構造体型の任意の有効なフィールドセレクタにできるようになりました。トップレベルのフィールド名だけでなく。実際には、これは埋め込み構造体から来るプロモートされたフィールドを、埋め込み型を明示することなく直接設定できることを意味します。
```go
type Base struct { ID int }
type User struct { Base Name string }
```
Go 1.27 より前は、`User{Base: Base{ID: 7}, Name: "Mittens"}` と書く必要がありました。今では、プロモートされた ID がそれ自体でキーとして機能します。
```go
u := User{ID: 7, Name: "Mittens"}
fmt.Println(u.ID, u.Name)
```
出力:
```
7 Mittens
```
汎用的な関数型推論
ジェネリック関数が、マッチする関数型が期待されるすべてのコンテキストで使用される場合に、関数型推論が一般化されました。以前は変数への単純な代入(これはすでに機能していました)だけでなく、変換や複合リテラルでも同様です。これらの場合、以前は型引数を手動で指定する必要がありました。
2 つのジェネリックヘルパー関数を取り、それらを `func([]int) int` の要素型を持つスライスにドロップしてみましょう。
```go
func first[T any](s []T) T { return s[0] }
func last[T any](s []T) T { return s[len(s)-1] }
```
// スライスの要素型が推論を駆動します: 各エントリで T=int。
// Go 1.27 より前は、「ジェネリック関数をインスタンス化なしで使用することはできません」というエラーになりました。
// first[int], last[int] と書く必要がありました。
ops := []func([]int) int{first, last}
for _, op := range ops {
fmt.Println(op([]int{10, 20, 30}))
}
```
出力:
```
10
30
```
𝗗 高速なメモリ割り当て
コンパイラは現在、サイズ特化型のメモリ割り当てルーチンへの呼び出しを生成し、小さな(80バイト未満の)割り当てのコストを最大30%削減しています。改善はワークロードによって異なりますが、全体的な増加は実際のメモリ割り当てが多いプログラムで約1%になると予想されます。トレードオフとして、ワークロードに関係なく、約60KBのバイナリサイズの増加があります。
コードを変更する必要はありません。少し速くなるだけです。無効にしたい場合は、`GOEXPERIMENT=nosizespecializedmalloc` でビルドしてください。このオプトアウトは Go 1.28 で削除される予定です。
𝗗 Goroutine ラベル付きトレースバック
`go.mod` が Go 1.27 以降を設定しているモジュールの場合、トレースバックには `runtime/pprof` のゴルーチンラベルが各ゴルーチンのヘッダー行に含まれるようになりました。pprof.Do を使用してプロファイリングのためにすでにラベルを付けている場合、そのコンテキストがクラッシュダンプ、SIGQUITトレース、および `runtime.Stack` の出力にも表示されるようになります(それ以外は同一のゴルーチンを区別するのに便利です)。
ここでラベルを付け、現在のゴルーチンのスタックをダンプして確認してみましょう。
```go
ctx := context.Background()
pprof.Do(ctx, pprof.Labels("request", "42"), func(ctx context.Context) {
buf := make([]byte, 1<<12)
n := runtime.Stack(buf, false)
fmt.Printf("%s", buf[:n])
})
```
出力例(ポインタ引数、オフセット、ファイルパスは実行ごとに異なります):
```
goroutine 1 [running] {request: 42}:
main.main.func1(...)
.../main.go:14 +0x38
runtime/pprof.Do(...)
.../runtime/pprof/runtime.go:57 +0x8c
main.main()
.../main.go:12 +0x6c
```
`[running]` の状態の直後に `{request: 42}` が追加されているのが新しい点です。これは pprof ラベルです。同じ `{...}` アノテーションが、パニックまたは SIGQUIT トレースバック内のすべてのラベル付きゴルーチンのヘッダーに表示されます。無効にするには `GODEBUG=tracebacklabels=0` を使用します(この設定は Go 1.26 で追加されました)。ラベルにトレースバックに含めたくない機密データが含まれる可能性がある場合のために、このオプトアウトは無期限に維持される予定です。
𝗗 Goroutine リークプロファイル
Go 1.26 では、実験的にゴルーチンリーク検出器が導入されました。Go 1.27 では、これは通常のプロファイルに昇格しました。`runtime/pprof` は、GCサイクルを実行して永久にブロックされている(リークした)ゴルーチンを見つけ、そのスタックを報告する `goroutineleak` プロファイルを提供します。もはや `GOEXPERIMENT` は不要です。
「リークした」ゴルーチンとは、チャネルやミューテックスなどで永遠にブロックされており、進行する手段がないゴルーチンのことです。典型的な例は、自身が保持するチャネルに送信するゴルーチンです。これにより、誰もそれを受信できなくなります。
```go
func leak() {
ch := make(chan int) // このゴルーチンだけが ch を参照します
ch <- 1 // 永遠にブロックされます: 誰も受信しません
}
```
1 つ起動し、それがパークするのを待ち、プロファイルをダンプします。
```go
go leak() // このゴルーチンは決して終了できません
runtime.Gosched() // 送信でパークさせます
// GC バックのスキャンは、進行できないゴルーチンを見つけます。
pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
```
出力例:
```
goroutineleak profile: total 1
1 @ 0x...
0x... main.leak+0x27
.../main.go:11
```
`total 1` という行は、検出器が正確に 1 つのリークしたゴルーチンを見つけたことを示しており、スタックはその `main.leak` にピン留めされています。これは決して完了しない `ch <- 1` 送信です(アドレスは実行ごとに異なります)。実際のサービスでは、標準出力に書き出すのではなく、通常 `/debug/pprof/goroutineleak` の `net/http/pprof` エンドポイントをスクレイピングするでしょう。
𝗗 Post-quantum signatures
新しい `crypto/mldsa` パッケージは、FIPS 204 で指定されたポスト量子デジタル署名スキームである ML-DSA を実装しています。これは 3 つのパラメータセット(MLDSA44、MLDSA65、MLDSA87)で提供され、セキュリティレベルと引き換えにキー/署名サイズを取引します。
```go
priv, _ := mldsa.GenerateKey(mldsa.MLDSA65())
msg :=
```