---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27、あるいは私がいかにして心配するのをやめてジェネリックメソッドを愛するようになったか
description: ジェネリックメソッド、encoding/json/v2、標準のuuidパッケージ、そして実際に機能するgoroutineリーク検出器。すべて実際に実行された例を用いたGo 1.27の概要紹介。
language: ja
date: 2026-08-15T02:08:43.129534158Z
path: /blog/posts/go-1-27-or-how-i-learned-to-stop-worrying-and-love-generic-methods-za800e454
category: release
---

![Go 1.27の概要](/assets/images/whats-new-in-go1.27/overview.webp)

Go 1.27は2026年8月にリリースされます。今回は言語そのものが変わりました。「`slices`に新しい関数を追加しました」といった程度の変更ではなく、実際に言語が変わったのです。ジェネリックメソッドが導入され、11年間開かれていたissueがクローズされました。`encoding/json`は、飛行機が空を飛んでいる間に新しいエンジンへ静かに置き換えられました。

以下のすべての例は`go1.27rc3`（darwin/arm64）で実行しました。すべての出力ブロックは実際の出力をそのままコピーしたもので、エラーメッセージも含みます。何か奇妙に見えるとしたら、それはコンパイラが実際にそう出力したからです。

```bash
go install golang.org/dl/go1.27rc3@latest
go1.27rc3 download
```

## 1. 言語が変わった（本当に）

### 1.1. ジェネリックメソッド

Go 1.18以降ジェネリクスは存在していましたが、Go 1.18以降、私たちは「あの会話」を繰り返してきました。それは次のようなものです。

> 「スライス型に`Map`メソッドを書いてみよう——」
>
> `method must have no type parameters`
>
> 「……わかった。パッケージレベルの関数にしよう。」

メソッドが使えるのは、レシーバーで宣言された型パラメータだけでした。独自のメソッドで新たな型パラメータを導入することはできませんでした。そのため、あらゆるジェネリックな変換処理はパッケージスコープへ追いやられ、`MapSlice`の何らかの変種という名前の、浮いたヘルパー関数が17個も並ぶことになりました。

Go 1.27ではこれが解決されました。

```go
type List[E any] []E

// Fは、メソッド自身が宣言する型パラメータです
func (l List[E]) Apply[F any](f func(E) F) List[F] {
	r := make(List[F], len(l))
	for i, x := range l {
		r[i] = f(x)
	}
	return r
}

func main() {
	l := List[int]{1, 2, 3}
	fmt.Println(l.Apply(func(i int) string { return fmt.Sprint(i * 10) }))
	// [10 20 30]
}
```

レシーバー自体がジェネリックである必要さえありません。ごく普通の構造体でも、ジェネリックメソッドを持てます。

```go
type Bag struct{ items []any }

func (b *Bag) Add[T any](v T) { b.items = append(b.items, v) }
```

ただし、今日の午後にコードベース全体をリファクタリングする前に、**2つの制約**があることを知っておいてください。見た目以上に重要です。

1. インターフェースのメソッドは型パラメータを宣言できません。
2. ジェネリックメソッドはインターフェースのメソッドを実装できません。

問題になるのは2つ目です。

```go
type Adder interface{ Add(int) int }

type G struct{}

func (G) Add[T any](v T) T { return v }

var _ Adder = G{}
```

```
cannot use G{} (value of struct type G) as Adder value in variable declaration:
	G does not implement Adder (wrong type for method Add)
		have Add[T any](T) T
		want Add(int) int
```

つまり、ジェネリックメソッドと動的ディスパッチは共存しません。これはGoチームがけちなのではなく、ジェネリックメソッドには無限個のインスタンス化が存在しうる一方、インターフェースのメソッドテーブルは有限かつコンパイル時に確定している必要があるからです。無限のものを有限のテーブルに入れることはできません。宇宙がそれを許さないのです。

実践的な結論として、ジェネリックメソッドは、ユーティリティ的なAPIを持つ具象型のためのものです。インターフェースの背後に隠す必要があるものには、従来の方法がまだ必要です。

標準ライブラリはすでにこの機能を活用しています。`math/rand/v2`の`Rand`にジェネリックメソッドが追加されました。

```go
func (r *Rand) N[Int intType](n Int) Int
```

```go
r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.N(int32(100)))     // 76
fmt.Println(r.N(time.Second))    // 616.436223ms
```

以前はパッケージレベルの`rand.N`だけがジェネリックで、それはグローバルなソースを使うことを意味していました。今では、自分でシードした`*Rand`でも同じ利便性が得られます。小さな変更ですが、良い変更です。

### 1.2. 構造体リテラルのフィールドセレクタ、または: Issue #9859 がついに解決される

埋め込みにより`u.ID`と書けます。しかし、埋め込みによって`User{ID: 1}`と書けるわけではありません。代わりに書くのは`User{Base: Base{ID: 1}}`であり、これはGoが「本当にそのつもりか」を問うための方法です。

[Issue #9859](https://go.dev/issue/9859)は2015年に起票されました。このたびクローズされました。どこかで、その後2回転職したgopherが、GitHubの通知を受け取っているはずです。

```go
type Object struct{ name, color string }
type Point3D struct {
	Object
	x, y, z float64
}
type Line struct {
	Object
	p, q Point3D
}

// Go 1.27: 昇格したフィールドを直接使用します
line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
```

`name`は`Line`のフィールドではありません。埋め込まれた`Object`のフィールドです。以前は`Line{Object: Object{name: "diagonal"}, ...}`と書く必要がありました。

ここからは細則です。これらすべてを実際にコンパイルして確認しました。

**(a) キーは依然としてただの識別子です。** 任意のセレクタパスは書けません。

```go
_ = Line{Object.name: "x"}   // invalid field name Object.name in struct literal
_ = Line{p.x: 1}             // invalid field name p.x in struct literal
```

したがって、この機能は「キーがフィールドアクセスのように動くようになった」ではありません。正確には「暗黙的に昇格したフィールド名が許可された」ということです。見出しが示唆するよりも限定的です。

**(b) 埋め込みフィールドと、そこから昇格したフィールドを同時に指定することはできません。**

```go
obj := Object{"edge", "black"}
_ = Line{Object: obj, name: "diagonal"}
// cannot specify promoted field name and enclosing embedded field Object
```

妥当な判断です。同じメモリについて矛盾する2つの指定がなされたため、コンパイラは推測することを拒否しました。

**(c) ポインタ埋め込みは推奨されていません。**

```go
type PtrEmbed struct {
	*Object
	z int
}

_ = PtrEmbed{name: "x"}
// invalid implicit pointer indirection to reach name
```

ポインタをたどるには、たどるべきポインタが必要ですが、リテラルを構築する時点ではまだ存在しません。これも妥当です。

良い知らせとして、`go fix`が古いリテラルを書き換えてくれます。詳しくは後述します。

### 1.3. 関数の型推論がより恣意的でなくなった

ジェネリック関数の型推論は、ある場所では動き、別の場所では動きませんでした。どちらになるかを決める明確な原則はありませんでした。変数に代入するのは問題ない。全く同じ関数を構造体のフィールドに入れると？コンパイルエラーになり、「2022年のように`double[int]`と書いてください」と言われました。

Go 1.27では、**目標の型が曖昧なく分かるあらゆる文脈で**推論が機能するようになりました。

```go
func double[T ~int | ~float64](v T) T { return v * 2 }

type S struct{ f func(int) int }
type A [1]func(int) int

func main() {
	s := S{f: double}          // 1.26: double[int]が必要でした
	a := A{double}             // 1.26: double[int]が必要でした

	c := make(chan func(int) int, 1)
	c <- double                // 1.26: double[int]が必要でした

	var fn func(float64) float64 = double  // これは常に動作していました

	fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
	// 42 10 14 3
}
```

1つの`double`が、3か所では`func(int) int`として、4か所目では`func(float64) float64`としてインスタンス化され、すべて文脈から決まります。関数値で満たされたオプション構造体やハンドラテーブルを書いているなら、コードベースから多くの`[T]`の雑音が消えるでしょう。

## 2. ランタイム: 無料のパフォーマンスとリーク検出器

### 2.1. サイズ特化のアロケーション

コンパイラは、小さなオブジェクトに対して、サイズに特化したアロケーションルーチンを呼び出すようになりました。リリースノートでは、80バイト未満のアロケーションで最大30%の削減、アロケーションを多用するプログラム全体では約1%の改善が主張されています。

測定せずにリリースノートを信じることはしないので、次のように試しました。

```go
type small struct{ a, b, c, d int }

var sink any

func BenchmarkAlloc(b *testing.B) {
	for b.Loop() {
		sink = &small{}
	}
}

func BenchmarkSlice(b *testing.B) {
	for b.Loop() {
		sink = make([]byte, 48)
	}
}
```

```
# デフォルト（サイズ特化malloc有効）
BenchmarkAlloc-12     3000000     5.358 ns/op
BenchmarkSlice-12     3000000    13.58 ns/op

# GOEXPERIMENT=nosizespecializedmalloc
BenchmarkAlloc-12     3000000     8.722 ns/op
BenchmarkSlice-12     3000000    20.81 ns/op

```

Apple Silicon上で、アロケーションだけを行うマイクロベンチマークでは30〜35%高速でした。つまり、これは最良のケースにすぎず、数字を良く見せるために設計されたベンチマークが、実験室条件の下で達成したものです。実際のプログラムでは、GCや実際の処理がその効果のほとんどを埋もれさせるでしょう。正直な主張は約1%のほうです。

コストはバイナリサイズです。Hello Worldでは次のとおりです。

```
2413202 bytes  (デフォルト)
2361826 bytes  (nosizespecializedmalloc)
```

約50KBが固定で増加し、プログラムの内容には左右されません。`GOEXPERIMENT=nosizespecializedmalloc`でオプトアウトできますが、**この回避策はGo 1.28で削除される予定です**。ですから、生活習慣としてではなく、バグ報告の回避策として扱ってください。

### 2.2. goroutineリークプロファイルが実用になった

Go 1.26では実験的機能でしたが、1.27で一般提供されました。`goroutineleakprofile`のGOEXPERIMENTは不要になり、そのまま動作します。

```go
func leak() {
	ch := make(chan int) // 誰も送信することはありません。誰も。
	go func() {
		<-ch
	}()
}

func main() {
	for range 3 {
		leak()
	}
	runtime.GC()
	runtime.GC()
	pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
}
```

```
goroutineleak profile: total 3
3 @ 0x104de3f18 0x104d7f0b0 0x104d7ec34 0x104e374f4 0x104dea024
#	0x104e374f3	main.leak.func1+0x23	/tmp/go127/leak/main.go:12
```

永久に停止した3つのgoroutineが、まさにそれを作った場所を示すファイル名と行番号とともに表示されています。

この仕組みの背後にある工夫は本当に巧妙です。**ガベージコレクタの到達可能性解析を再利用**しています。goroutine GがプリミティブPでブロックされており、Pが実行可能なgoroutine（あるいはそれらが起こしうるもの）のいずれからも到達不能であれば、Pには二度と触れることができず、Gが目を覚ますことは決してありません。これはヒューリスティックではなく、証明です。

同じ設計により、その制約も自動的に得られます。チャネルやミューテックスがグローバル変数を通じて、あるいは実行中のgoroutineのローカル変数を通じて到達可能であれば、GCはそれを見ることができるため、ランタイムは何も結論づけられません。上の簡単な例でさえ、報告を得るためにruntime.GC()を2回呼ぶ必要があることに注意してください。すべてを捕捉できるわけではありません。

しかし、古典的な「コンテキストのキャンセルを忘れ、ワーカーのgoroutineが永久に生き続ける」という問題は捕捉できます。ほとんどの場合、これがリークの大部分です。

`net/http/pprof`をインポートすると、`/debug/pprof/goroutineleak`でも利用できます。これをステージング環境に組み込み、毎週確認して、静かに戦慄してください。

この作業はUberのVlad Saiocによって貢献されました。好きな飲み物を一杯おごるべき人物です。

### 2.3. トレースバックで、どのリクエストが死んだかがわかるようになった

Go 1.27以降を宣言したモジュールでは、トレースバックのヘッダー行に`runtime/pprof`のgoroutineラベルが含まれるようになりました。

```go
func handle(ctx context.Context) {
	var wg sync.WaitGroup
	wg.Go(func() {
		buf := make([]byte, 4<<10)
		os.Stdout.Write(buf[:runtime.Stack(buf, true)])
	})
	wg.Wait()
}

func main() {
	labels := pprof.Labels("request", "GET /orders/42", "tenant", "acme")
	pprof.Do(context.Background(), labels, handle)
}
```

```
goroutine 3 [running] {request: "GET /orders/42", tenant: acme}:
main.handle.func1()
	/tmp/go127/tblabel/main.go:15 +0x40
sync.(*WaitGroup).Go.func1()
	...
goroutine 1 [sync.WaitGroup.Wait] {request: "GET /orders/42", tenant: acme}:
...
```

重要な点は、**ラベルは子goroutineに継承される**ことです。goroutine 3はラベルを一度も設定していません。親から受け取ったのです。

ですから、次に本番環境でデッドロックが起き、誰かがプロセスにSIGQUITを送ったとき、全く同じに見える4,000個のgoroutineの代わりに、どのリクエストとどのテナントに属するかタグ付けされた4,000個のgoroutineが得られます。リクエストの入口で`pprof.Do`を3行書くだけで、それが手に入ります。

ラベルには標準エラーに出力したくないものを含めることができるため、`GODEBUG=tracebacklabels=0`で無効にできます。そのオプトアウトは、永久に残ることが明示されています。

### 2.4. asynctimerchanは完全に削除された

Go 1.23はタイマーチャネルをバッファなし（同期）にし、古い動作に戻すための手段として`asynctimerchan=1`を提供していました。1.27では、この設定が**恒久的に削除**されました。`time`パッケージのチャネルは、交渉の余地なく同期的です。

それに伴って導入されたポリシーが興味深いところです。削除されたGODEBUGが`go.mod`に残っていても、自動的にビルドが壊れることはありません。**古い**値に設定されている場合にのみ壊れます。

```
# go.modの内容: godebug asynctimerchan=1
go: error loading go.mod:
go.mod:5: removed GODEBUG "asynctimerchan" set to old value "1" (https://go.dev/doc/godebug#go-127)

# go.modの内容: godebug asynctimerchan=0
(正常にビルドされます)
```

つまり、叱られるのは、削除された動作に実際に依存していた人だけです。最終的なデフォルト値に設定して忘れていた人は、そのことを考えずに済み続けます。これは思慮深いAPIの考古学といえるでしょう。

## 3. 標準ライブラリ

### 3.1. encoding/json/v2: 飛行中にエンジンが置き換えられた

これが大きな変更です。[2年以上にわたる提案の議論](https://go.dev/issue/71497)が、ついに形になりました。

現在、**3つのパッケージ**があり、その分割を理解することがほぼ全てです。

| パッケージ               | 役割                                                                           |
| ------------------------ | ------------------------------------------------------------------------------ |
| `encoding/json`          | おなじみのv1 API。**動作は100%変更なし。** 現在はv2の上に実装されています       |
| `encoding/json/v2`       | 意味論的処理。Goの値 ↔ JSON                                                    |
| `encoding/json/jsontext` | 構文的処理。トークンストリームとしてのJSON                                      |

見出しとなるのは、`encoding/json`が全く異なる実装の上に再構築されたにもかかわらず、**動作は完全に同一である**という点です。仕組みは次のとおりです。

```go
// Go 1.27のencoding/json.Unmarshalの要約
func Unmarshal(data []byte, v any) error {
	return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
}
```

v1の従来の癖はすべてオプションとしてエンコードされ、`DefaultOptionsV1()`にまとめられています。v1 APIは常にこのまとまりを適用します。したがって、次のコードは1.26でも1.27でも同じように動作します。

```go
var m map[string]int
json.Unmarshal([]byte(`{"a":1,"a":2}`), &m)  // <nil>, map[a:2]
```

そうです。v1は依然として重複キーを黙って受け入れ、最後の値を採用します。これは昔からそうでしたし、今もそうです。互換性とは、悪い部分との互換性でもあるのです。

**一方、v2には明確な方針があります。**

```go
var m map[string]int
jsonv2.Unmarshal([]byte(`{"a":1,"a":2}`), &m)
// jsontext: duplicate object member name "a"

var s string
jsonv2.Unmarshal([]byte("\"\xff\""), &s)
// jsontext: invalid UTF-8 after offset 1
```

重複キーと不正なUTF-8は拒否されます。どちらも単に見苦しいだけでなく、本当に危険です。2つのパーサーが、どの重複キーが勝つかについて食い違うと、セキュリティバグが生じます。CouchDBのCVE-2017-12635はまさにこれでした。`roles`キーが2つ含まれるJSONボディに対し、検証器は一方を読み、ストレージ層はもう一方を読みました。拒否するのが正しい判断です。

オプションは可変長引数で渡します。

```go
type Config struct {
	Name    string   `json:"name"`
	Tags    []string `json:"tags,omitzero"`
	Timeout int      `json:"timeout,omitzero"`
}

out, _ := jsonv2.Marshal(Config{Name: "api"})
// {"name":"api"}

// マップのキーをソートし、インデントを付ける
out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
// {
//   "a": 1,
//   "b": 2
// }

// 未知のフィールドを拒否する。以前はDecoderとDisallowUnknownFields()が必要でした
var c Config
err := jsonv2.Unmarshal([]byte(`{"name":"api","nope":1}`), &c,
	jsonv2.RejectUnknownMembers(true))
// json: cannot unmarshal JSON string into Go main.Config:
//   unknown object member name "nope"
```

`Deterministic`、`MatchCaseInsensitiveNames`、`StringifyNumbers`、`FormatNilSliceAsNull`、`OmitZeroStructFields`。以前はサードパーティのライブラリや手書きの`MarshalJSON`で解決していた事柄の多くが、今やフラグで指定できます。

そして、移行の話こそが最良の部分です。**後に指定したオプションが優先される**ため、v2の厳格さを一度に1つの動作ずつ採用できます。

```go
// v1の意味論を維持しつつ、v2と同様に重複キーを拒否する
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// duplicate object member name
```

重複キーの受け入れを止めるために、200,000行のコードベースをv2へ移植する必要はありません。オプションを1つ切り替えるだけです。大規模なコードベースにとって、これは現実的な道筋です。

性能面では、マーシャリングはほぼ同等で、**アンマーシャリングは大幅に高速化**されています。問題が起きた場合は、`GOEXPERIMENT=nojsonv2`で古い実装に戻せます。このオプトアウトもいずれ削除される予定ですので、そのまま居座るのではなく、issueを立ててください。

`jsontext`は低レベルの層です。`Encoder`/`Decoder`が、状態機械によって誠実さを保ちつつ、JSONを`Token`と`Value`として走査します。Goの値を一切実体化したくない、ストリーミング変換器やJSONフィルタを書く場合に利用してください。

### 3.2. 標準のuuidパッケージ

ついに実現しました。RFC 9562が標準ライブラリに入り、`go get`は不要です。

```go
import "uuid"

func main() {
	fmt.Println(uuid.NewV4())
	// b97aa695-da08-472c-af81-ff088129019f

	// v7: 上位48ビットはタイムスタンプなので、作成順に必ずソートされます
	a, b := uuid.NewV7(), uuid.NewV7()
	fmt.Println(a.Compare(b) < 0) // true

	u, err := uuid.Parse("urn:uuid:0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b")
	fmt.Println(u, err)
	// 0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b <nil>

	fmt.Println(uuid.Nil(), uuid.Max())
	// 00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff
}
```

API全体は`New`、`NewV4`、`NewV7`、`Nil`、`Max`、`Parse`、`MustParse`と、型1つ、`type UUID [16]byte`だけです。それ以上ありません。コーヒーが冷めるまでの間に、パッケージのドキュメント全体を読み終えられます。

知っておく価値のある詳細は次のとおりです。

- **`UUID`は`[16]byte`**なので、`==`が使え、そのままマップのキーとして使えます。`google/uuid`と同じ設計です。
- **`Nil`と`Max`は変数ではなく関数です。** パッケージレベルの`var Nil UUID`は足を狙った装填済みの銃のようなもので、どこかの誰かがいずれ代入してしまうからです。
- ランダムビットは**暗号学的に安全な**生成器から得られます。
- `encoding.TextMarshaler`/`TextUnmarshaler`/`TextAppender`を実装しているため、JSONの構造体にそのまま組み込めます。
- **データベースの主キーには`NewV7`を使うべきです。** 時間順なので、B木インデックスが断片化しなくなります。これはv4 UUIDが悪名高いほど苦手とする点です。

依存関係を1つ削除できるようになりました。v1/v3/v5が必要な場合や、より高度なパース・オプションが必要な場合は、サードパーティのライブラリにはまだ役割があります。

### 3.3. crypto/mldsa: 耐量子署名。ただし非常に大きい

Go 1.24では、耐量子鍵交換のための`crypto/mlkem`が提供されました。Go 1.27はもう一方の半分、FIPS 204として標準化されたML-DSA署名をもたらします。

```go
sk, _ := mldsa.GenerateKey(mldsa.MLDSA65())
pk := sk.PublicKey()

msg := []byte("release the gophers")
opts := &mldsa.Options{Context: "gosuda.org/blog"}

sig, _ := sk.Sign(rand.Reader, msg, opts)

fmt.Println("private key seed:", len(sk.Bytes()), "bytes")  // 32
fmt.Println("public key:", len(pk.Bytes()), "bytes")        // 1952
fmt.Println("signature:", len(sig), "bytes")                // 3309

fmt.Println(mldsa.Verify(pk, msg, sig, opts))
// <nil>
fmt.Println(mldsa.Verify(pk, msg, sig, &mldsa.Options{Context: "other"}))
// mldsa: invalid signature
```

この数字を見てください。**1つの署名は3,309バイト**です。Ed25519は64バイトです。50倍の増加であり、公開鍵はさらに2KB近くあります。これらを証明書チェーンにいくつか詰め込むと、TLSハンドシェイクには独自のMTU戦略が必要になり始めます。

これが、今日の耐量子性の実際のコストであり、誰もが明日すべてを切り替えないのはそのためです。しかし、標準ライブラリに入ったので、必要になる_前に_そこにあるのが望ましい状態です。

`Options.Context`はドメイン分離です。同じ鍵を異なる目的で署名する際、それぞれ異なるコンテキストを使えば、あるコンテキストでの署名は別のコンテキストでは検証されません。上の例がまさにそれを示しています。同じ鍵、同じメッセージ、異なるコンテキストで、拒否されました。

`PrivateKey`は`crypto.Signer`を実装しているため、既存のインターフェースにそのまま組み込めます。`crypto/x509`はML-DSAの鍵と署名を扱え、`crypto/tls`はTLS 1.3において`MLDSA44`/`MLDSA65`/`MLDSA87`の署名方式をサポートします。

また、`SignDeterministic`もあります。これは乱数を使わないため、テストや再現可能なビルドに便利です。

### 3.4. simd: アセンブリなしのベクトル命令

Go 1.26では、アーキテクチャ固有の`simd/archsimd`が実験的に導入されました。Go 1.27では、**移植可能でベクトル幅に依存しない**`simd`が追加されます。`GOEXPERIMENT=simd`で有効化します。

```go
// dst = a*x + y
func axpy(dst, x, y []float32, a float32) {
	va := simd.BroadcastFloat32s(a)
	w := va.Len()

	i := 0
	for ; i+w <= len(x); i += w {
		vx := simd.LoadFloat32s(x[i:])
		vy := simd.LoadFloat32s(y[i:])
		vx.MulAdd(va, vy).Store(dst[i:])
	}
	// 末尾は部分的なロード/ストアで処理する。スカラーの後処理ループは不要
	if i < len(x) {
		vx, _ := simd.LoadFloat32sPart(x[i:])
		vy, _ := simd.LoadFloat32sPart(y[i:])
		vx.MulAdd(va, vy).StorePart(dst[i:])
	}
}
```

```bash
$ GOEXPERIMENT=simd go1.27rc3 run ./simddemo
vector bits: 128 emulated: false
[4 7 10 13 16 19 22 25 28 31]
```

重要な設計上の選択は、**ベクトル幅がハードコードされない**ことです。実行時に`va.Len()`を問い合わせます。`VectorBitSize()`は実際の幅を、`Emulated()`は実際のハードウェアを使っているのか、それとも丁寧なソフトウェアによる模倣なのかを教えてくれます。私のMacは128ビットのNEONを報告しました。AVX-512のマシンはより大きな値を報告し、何もないマシンはエミュレーションを報告しますが、それでもコードは動作します。

`LoadFloat32sPart`/`StorePart`は特筆に値します。手書きのSIMDで最も面倒なのは常に、配列末尾の端数部分ですが、これは別個のスカラーループなしにそれを処理します。

まだ実験的な段階で、APIも不安定ですので、本番環境には入れないでください。しかし、これがアセンブリやcgoではなくGoのコードであるという事実は、本当に大きな進歩です。

### 3.5. hash/maphash.Hasher

値の型とハッシュベースのコンテナとの間の契約を記述する、新しいインターフェースです。

```go
type Hasher[T any] interface {
	Hash(*Hash, T)
	Equal(x, y T) bool
}
```

なぜでしょうか。Goの組み込みマップは`comparable`なキーしか受け付けないからです。スライスをキーにすることはできません。「大文字小文字を無視して等しい」と定義することもできません。`Hasher`は両方を一度に解決します。

```go
type CaseInsensitive struct{}

func (CaseInsensitive) Hash(h *maphash.Hash, s string) {
	h.WriteString(strings.ToLower(s))
}
func (CaseInsensitive) Equal(x, y string) bool {
	return strings.ToLower(x) == strings.ToLower(y)
}

var seed = maphash.MakeSeed()

func hashOf[T any](hr maphash.Hasher[T], v T) uint64 {
	var h maphash.Hash
	h.SetSeed(seed)   // 同じシード。そうでなければこの全体が意味を持ちません
	hr.Hash(&h, v)
	return h.Sum64()
}

fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
// true
```

通常の`==`の意味論に対しては、`ComparableHasher[T]`があります。

```go
hashOf(maphash.ComparableHasher[int]{}, 42)
```

注意点として、**`Hasher`はインターフェースであり、データ構造ではありません。** これを利用する標準のハッシュテーブルやブルームフィルタは、まだ存在しません。これは将来のコンテナパッケージへの下準備です。現時点では、独自の構造を構築するときに使うか、`go/types.Hasher`のような既存の実装を利用します（これにより、`types.Type`を`Identical`を尊重しながらマップのキーとして使えます）。

シードの管理は利用者の責任です。上の`hashOf`が呼び出しごとに新しいシードを作っていたら、同じ値でも異なるハッシュになり、例は`false`を出力するでしょう。これは私が最初に試したときに書いてしまったバグそのものです。コンテナごとにシードは1つです。（シードはハッシュフラッディングによるDoS攻撃を防ぐためにランダム化されているので、単なる定数ではありません。）

### 3.6. httptest.NewTestServer + synctest.Sleep: スリーパーの一撃

このリリースから1つだけ採用するなら、これにしてください。

`testing/synctest`はGo 1.25で正式版となり、並行コードをテストするための疑似クロックを提供しました。ただし、テストが実際のネットワークに触れた瞬間、その幻想は崩れました。Go 1.27の`httptest.NewTestServer`は**インメモリの疑似ネットワーク**を使うため、バブルは保たれます。そして`synctest.Sleep`（`time.Sleep` + `synctest.Wait`と同等）が、それを完成させます。

指数バックオフ付きのリトライのテストは次のとおりです。

```go
func TestRetryWithBackoff(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		var hits int
		srv := httptest.NewTestServer(t, http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				hits++
				if hits < 3 {
					w.WriteHeader(http.StatusServiceUnavailable)
					return
				}
				io.WriteString(w, "ok")
			}))

		client := srv.Client()
		start := time.Now()
		var body string
		for attempt := range 5 {
			resp, err := client.Get(srv.URL)
			if err != nil {
				t.Fatal(err)
			}
			b, _ := io.ReadAll(resp.Body)
			resp.Body.Close()
			if resp.StatusCode == http.StatusOK {
				body = string(b)
				break
			}
			synctest.Sleep(time.Duration(1<<attempt) * time.Second)
		}

		if body != "ok" {
			t.Fatalf("got %q", body)
		}
		// バックオフは正確に1s + 2s = 3sであるべき
		if elapsed := time.Since(start); elapsed != 3*time.Second {
			t.Fatalf("elapsed = %v, want 3s", elapsed)
		}
		t.Logf("hits=%d elapsed=%v", hits, time.Since(start))
	})
}
```

```
=== RUN   TestRetryWithBackoff
    x_test.go:48: hits=3 elapsed=3s
--- PASS: TestRetryWithBackoff (0.00s)
```

二度読んでください。実際のHTTPサーバーに対して3秒間のバックオフをシミュレートしたのに、**0.00秒**で終わりました。そして、アサーションは`elapsed == 3*time.Second`です。「少なくとも3秒、スケジューラのジッターを考慮して」ではなく、_正確に_3秒です。疑似クロックはジッターを起こしません。

`synctest.Sleep`には特定の理由があります。テストが、テスト対象のコードと同じ時間だけスリープする場合、どちらが先に目を覚ますかは誰にもわかりません。`synctest.Sleep`はスリープし_、そのうえで_バブル内の他のすべてのgoroutineが確実にブロックされるまで待つため、システムが落ち着いた後の状態を観測できます。

タイムアウト、リトライ、サーキットブレーカー、レートリミッタ。所有する時間依存のHTTPクライアントのテストはすべて、高速かつ決定的になり得ます。現在のテストスイートが`time.Sleep(100 * time.Millisecond)`と願望で成り立っているなら、これが脱出口です。

### 3.7. 本番環境に実際に影響するnet/httpの変更

静かな変更ですが、メトリクスには表れるでしょう。

**HTTP/1のレスポンスボディはCloseで自動的にドレインされます。** 読み残しのコンテンツは（保守的な上限まで）ボディを閉じたときにドレインされるため、コネクションを再利用できます。つまり、2016年以来、同じStack Overflowの回答から誰もがコピー＆ペーストしてきた次のおまじないを、ついに削除できます。

```go
// もはや不要
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

多くのプログラムでは、これは何もしないか、わずかな改善になります。もしそれで状況が_悪化_する場合は、リリースノートが控えめに説明する領域にいる可能性が高いです。`Transport.MaxIdleConns`が0に設定されている、あるいはリクエストごとに新しい`Client`を作り、アイドルコネクションの上限を完全に迂回している場合です。`Transport.DisableKeepAlives = true`で覆い隠すことはできますが、リリースノートの実際の助言は「より深く調べることがおそらく有益である」というもので、これはGoチームの言葉で言えば_もっと大きな問題があります_という意味です。

**HTTP/2クライアントの優先度（RFC 9218）。** サーバーはクライアントの優先度シグナルを尊重するようになりました。従来のラウンドロビン方式のスケジューリングを好む場合は、`Server.DisableClientPriority = true`を設定してください。

**`Server.MaxHeaderValueCount`。** サーバーが受け入れるヘッダー値の数に上限を設けます。デフォルトは`DefaultMaxHeaderValueCount`です。「1万個のヘッダーを送って、何が起きるか見てみよう」という手段がもう1つ閉じられました。

**ユーザー提供のコネクションでのALPN。** 独自の`net.Conn`が`ConnectionState() tls.ConnectionState`を実装していれば、`Transport`と`Server`はそれに対してTLSのALPNネゴシエーションを行います。そのため、プロキシを経由するカスタムダイアラーでも、HTTP/2をネゴシエートできます。

### 3.8. 小さいけれど嬉しい変更

**`strings.CutLast` / `bytes.CutLast`。** `Cut`は_最初の_区切り文字で分割します。「最後」版がなかったため、誰もが`LastIndex`とスライスを手書きし、私たちの約30%が最初の試行でオフバイワンのバグを出していました。

```go
name, ext, ok := strings.CutLast("archive.tar.gz", ".")
// archive.tar gz true
```

ファイル拡張子や`host:port`の解析に最適です。IPv6アドレスにはコロンが多く含まれるため、_最後の_コロンを見つけなければならない場面で役立ちます。

**`math/big.Int.Divide`。** 明示的な丸めモードを持つ除算です。「`Quo`と`Div`のどちらが欲しいのか」という運任せの選択を置き換えます。

```go
x, y := big.NewInt(-7), big.NewInt(2)

new(big.Int).Divide(x, y, new(big.Int), big.Trunc)  // q=-3 r=-1
new(big.Int).Divide(x, y, new(big.Int), big.Floor)  // q=-4 r=1
new(big.Int).Divide(x, y, new(big.Int), big.Round)  // q=-4 r=1
new(big.Int).Divide(x, y, new(big.Int), big.Ceil)   // q=-3 r=-1
```

丸め規則が規制に明文化されている分野で働いているなら、これはあなたのためのものです。

**`url.URL.Clone`と`url.Values.Clone`。** ディープコピーです。従来の`*u`によるシャローコピーは`Userinfo`ポインタを共有していたため、見つけるのに1日かかり、直すのに5秒しかからないような種類のバグを生んでいました。

```go
orig, _ := url.Parse("https://gosuda.org/blog?tag=go&tag=1.27")
clone := orig.Clone()
clone.Host = "example.com"
fmt.Println(orig.Host, clone.Host)  // gosuda.org example.com
```

**`net.UnixConn`** の読み取りメソッドは、`net.OpError`でラップせずに`io.EOF`を直接返すようになりました。`err == io.EOF`が、本来そうあるべきだったように動作するようになりました。

**`database/sql.ConvertAssign`と`driver.RowsColumnScanner`。** ドライバ作成者向けの機能です。前者は`Rows.Scan`が行う型変換を公開し、後者はドライバが中間的なアロケーションを省いて、ユーザーの変数に直接スキャンできるようにします。

**`unicode` 15 → 17。** 一度に2つのバージョンが上がりました。文字列の分類や正規化の動作がわずかに変わる可能性があります。それに依存するテストがあれば、すぐにわかるでしょう。

**`compress/flate`は高速化されました。** そして**その出力バイト列はGo 1.26と異なる可能性があります。** これは`archive/zip`、`compress/gzip`、`compress/zlib`、`image/png`にも波及します。圧縮出力のハッシュを取るゴールデンファイルのテストがあれば、それらは壊れるでしょうが、それはリグレッションではありません。アップグレードの_前に_確認してください。インシデントの振り返りの最中ではなく。

## 4. ツールチェーン

### 4.1. go fixにモダナイザーが増えた

`go fix`は静かにコードのモダナイズツールになりつつあります。Go 1.27では、`atomictypes`、`embedlit`、`slicesbackward`、`unsafefuncs`が追加されました。`-diff`でプレビューできます。

```go
package fixdemo

import (
	"sync"
	"sync/atomic"
)

type Base struct{ ID int }
type User struct {
	Base
	Name string
}

func New() User {
	return User{Base: Base{ID: 1}, Name: "gopher"}
}

var counter int64

func Incr() { atomic.AddInt64(&counter, 1) }

func Reverse(s []string) {
	for i := len(s) - 1; i >= 0; i-- {
		_ = s[i]
	}
}

func Spawn(wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		defer wg.Done()
	}()
}
```

```bash
$ go1.27rc3 fix -diff ./fixdemo
```

```diff
 import (
+	"slices"
 	"sync"
 	"sync/atomic"
 )

 func New() User {
-	return User{Base: Base{ID: 1}, Name: "gopher"}
+	return User{ID: 1, Name: "gopher"}
 }

-var counter int64
+var counter atomic.Int64

-func Incr() { atomic.AddInt64(&counter, 1) }
+func Incr() { counter.Add(1) }

 func Reverse(s []string) {
-	for i := len(s) - 1; i >= 0; i-- {
-		_ = s[i]
+	for _, v := range slices.Backward(s) {
+		_ = v
 	}
 }

 func Spawn(wg *sync.WaitGroup) {
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-	}()
+	wg.Go(func() {
+	})
 }
```

1つの小さなファイルで4つのモダナイザーが発動しました。

- `embedlit`: §1.2の新しい構文を使って、埋め込みリテラルを書き換えます
- `atomictypes`: `atomic.AddInt64(&x, 1)`は`atomic.Int64.Add(1)`になります。これにより非アトミックなアクセスが不可能になり、知らなかった32ビットのアライメントのバグも静かに修正されます
- `slicesbackward`: 後方へのループは`slices.Backward`になります
- `waitgroupgo`: `Add`/`go`/`Done`の定型句は`wg.Go`になります（曖昧さを避けるため、1.26の`waitgroup`から名前が変更されました）

`go tool fix help`は26個すべてを一覧表示し、`go tool fix help <name>`は1つを説明します。また、`fmtappendf`は「文体上の懸念」を理由に削除されました。これは、そのissueスレッドで何が起きたのかを、見事に外交的に表現したものです。

古いコードベースに対して`go fix ./...`を実行するのは、本当に満足のいく午後の作業です。もちろん、まず差分を読んでください。

### 4.2. go testはデフォルトでstdversionを実行する

これは、あなたの一日を中断させる可能性が最も高い変更です。

`go test`はデフォルトで`stdversion`のvetチェックを実行するようになり、`go.mod`の`go`ディレクティブが許容する範囲より新しい標準ライブラリのシンボルにフラグを立てます。

```go
// go.modには: go 1.24
package sv

import "strings"

func Ext(name string) string {
	_, ext, _ := strings.CutLast(name, ".")  // CutLastは1.27のシンボルです
	return ext
}
```

```
$ go1.27rc3 test ./...
# sv
./x.go:6:23: strings.CutLast requires go1.27 or later (module is go1.24)
FAIL	sv [build failed]
```

これにより、自分のマシン（最新のツールチェーン）では全て動くのに、CIやユーザーの古い環境で爆発する、という典型的な失敗パターンが防がれます。保守的な`go`ディレクティブを持つライブラリを公開しているなら、これは贈り物です。

### 4.3. go test -jsonにOutputTypeが追加された

`"Action":"output"`の行に、オプションの`"OutputType"`フィールドが付くようになりました。値は`"error"`、`"error-continue"`、`"frame"`のいずれかです。

```
'frame'  '=== RUN   TestFail\n'
None     '    x_test.go:6: hello\n'
'error'  '    x_test.go:7: boom\n'
'frame'  '--- FAIL: TestFail (0.00s)\n'
'frame'  'FAIL\n'
```

`t.Log`の出力（フィールドなし）、`t.Error`の出力（`error`）、フレームワークが生成する行（`frame`）が区別できるようになりました。テスト出力のどの行が「本物」かを判定するために正規表現を書いたことがある人は、それを削除できます。

### 4.4. go docの改善

**`package@version`構文。** モジュールに追加せずに、特定のバージョンのドキュメントを読めます。

```bash
$ go1.27rc3 doc golang.org/x/sync/errgroup@v0.10.0
package errgroup // import "golang.org/x/sync/errgroup"
...
```

アップグレード前に何が変わったかを確認するのに便利です。

**`-ex`とサンプルのソース表示:**

```bash
$ go1.27rc3 doc -ex strings | grep Example
    func ExampleClone()
    func ExampleCompare()
    func ExampleContains()
    func ExampleCut()
    func ExampleCutPrefix()
    ...

$ go1.27rc3 doc strings.ExampleCut
package main

import (
	"fmt"
	"strings"
)

func main() {
	show := func(s, sep string) {
		before, after, found := strings.Cut(s, sep)
		fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
	}
	show("Gopher", "Go")
	...
}

Output:
Cut("Gopher", "Go") = "", "pher", true
```

サンプルのソースと期待される出力が、ターミナル上で、来週の木曜日になってもまだ開いているであろうブラウザタブを開かずに確認できます。

### 4.5. go mod tidyがrequireブロックを整理する

`go 1.27`以降のモジュールでは、`go mod tidy`が散在する`require`ブロックを最大2つ（直接と間接）にまとめ、付随するコメントを保持します。

```go
// 変更前
module tidydemo

go 1.27

require golang.org/x/sync v0.10.0

// networking
require golang.org/x/net v0.33.0

require (
	golang.org/x/sys v0.28.0 // indirect
)

require golang.org/x/text v0.21.0 // indirect
```

```go
// 変更後: go mod tidy
module tidydemo

go 1.27

require (
	// networking
	golang.org/x/net v0.33.0
	golang.org/x/sync v0.10.0
)
```

`// networking`コメントが残っていることに注意してください。これは主に、**Gitのマージコンフリクトの解決**の後始末をします。迷子の`require`ブロックが生まれるのはそこです。チームで3人以上が依存関係を追加しているなら、`go.mod`のコンフリクトは目に見えて静かになるでしょう。

### 4.6. その他

- **`bzr`サポートを削除しました。** これが影響する場合は、ぜひその経緯を聞かせてください。
- **`go tool trace -http`は、ポートのみを指定した場合にlocalhostにバインドされます。** `-http=:6060`はもはや全インターフェースで待ち受けません。本当にそうしたい場合は`-http=0.0.0.0:6060`を使ってください。これは`go tool pprof`と揃えられ、時折起きる偶発的な公開プロファイラを防ぎます。
- **レスポンスファイル（`@file`）**が`compile`、`link`、`asm`、`cgo`、`cover`、`pack`でサポートされるようになりました。GCC互換の形式です。コマンドライン長の制限を超えてしまうビルドシステム向けです。Bazelの皆さん、こんにちは。

## 5. コンパイラ、リンカ、ポート

**コンパイラ。** `//line`ディレクティブの相対ファイル名は、`go/scanner`と同様に、含まれるファイルのディレクトリを基準に解決されるようになりました。コードジェネレータを書いている場合に関係します。

関数リテラル（クロージャ）のシンボル名も単純になりました。インライン化の有無にかかわらず同じ名前になり、同じリテラルの複数のインスタンスがバイナリ内でコードを共有することがあります。機能的な変更はありませんが、_例外_として、`reflect.Value.Pointer`で関数の同一性を比較しているコードは、以前より頻繁に「等しい」と判定するようになります。その比較はもともと正しくありませんでしたが、それを使っているなら、今度はより大きな声で嘘をつき始めます。

**リンカ。** 新しい`-macos`と`-macsdk`オプションで、macOSの`LC_BUILD_VERSION`ロードコマンドのOSとSDKのバージョンを設定できます。

**ポート。**

- **Darwinは現在、macOS 13 Ventura以降を必要とします。** これは1.26で発表されたとおりです。CIのランナーを確認してください。
- **PowerPC（`GOOS=linux GOARCH=ppc64`）はELFv2 ABIに切り替わりました。** Linuxカーネル3.13以降が必要です（RHEL7は3.10にバックポートしています）。cgo、PIE、外部リンクがサポートされるようになりました。cgoを使っているが静的な純粋なGoのバイナリが必要な場合は、`CGO_ENABLED=0`を設定してください。

## 6. アップグレードのチェックリスト

Go 1.27は互換性を真剣に扱っていますが、バージョンを上げる前に次の点を確認してください。

- [ ] **圧縮出力のゴールデンファイルテスト。** `compress/flate`はエンコーダが変わりました。gzip、zip、pngのバイト列が異なる可能性があります。
- [ ] **JSONのエラーメッセージ文字列に一致するテスト。** 動作は同一ですが、**エラーテキストは同一ではありません。**
- [ ] **`go.mod`にある削除されたGODEBUG。** `asynctimerchan`、`gotypesalias`、`tlsrsakex`、`tls3des`、`tls10server`、`tlsunsafeekm`、`x509keypairleaf`。これらは_古い_値に設定されている場合にのみビルドを失敗させます。
- [ ] **`stdversion`違反。** `go test`がこれらを検出するようになりました。保守的な`go`ディレクティブを持つライブラリを管理しているなら、早めに実行してください。
- [ ] **macOS 12以前のCIランナー。** サポートは終了しました。
- [ ] **関数リテラルのシンボル名に依存するテスト**、そして**`reflect.Value.Pointer`による関数の比較。**
- [ ] **`Transport.MaxIdleConns = 0`**、またはリクエストごとの`Client`。自動ドレインのレスポンスボディと組み合わさると、遅くなる可能性があります。

## 結びに

Go 1.27は、先送りされていた多くの保守作業が一度に回ってきたリリースです。

ジェネリックメソッドは、1.18のジェネリクス作業に欠けていたピースでした。構造体リテラルのフィールドセレクタは、11年間開かれていたissueを閉じました。`encoding/json/v2`は2年にわたる提案の議論の成果です。そして`uuid`は、ほぼすべてのGoプロジェクトが10年にわたって同じサードパーティ依存関係で埋めてきた穴を埋めました。

この機能から実際に価値を得るための優先順位を挙げるなら、次のとおりです。

1. **`httptest.NewTestServer` + `synctest.Sleep`**: 時間に依存するHTTPテストが高速かつ決定的になります。リリース全体で最も効果に対する労力が小さい機能です。
2. **goroutineリークプロファイル**: ステージング環境でエンドポイントを1つ公開し、謙虚さを覚悟してください。
3. **トレースバックのgoroutineラベル**: リクエストの入口に`pprof.Do`を3行書くだけで、次の午前3時のスタックダンプが読めるものになります。
4. **`go fix ./...`**: モダナイズはツールに任せましょう。
5. **`encoding/json/v2`**: 急ぐ必要はありません。一度に1つのオプションずつ採用してください。

`go1.27rc3`を入手して、今すぐテストスイートを実行してください。このチェックリストにあるすべての項目は、インシデントの最中よりも、火曜日の午後に発見するほうがはるかに快適です。

**参考文献**

- [Go 1.27 リリースノート](https://go.dev/doc/go1.27)
- [The Go Programming Language Specification](https://go.dev/ref/spec)
- [proposal: encoding/json/v2 (#71497)](https://go.dev/issue/71497)
- [proposal: portable SIMD (#78902)](https://go.dev/issue/78902)
- [proposal: hash/maphash.Hasher (#70471)](https://go.dev/issue/70471)
