ブログに戻る

Go 1.27 の新機能:完全ガイド

Go 1.27 では、ジェネリックメソッドと標準ライブラリの uuid パッケージが加わり、json/v2 がデフォルトのエンジンになりました。今すぐ採用すべきものと、アップグレード時に気づかないうちに壊れるものを解説します。

Go 1.27 の新機能:完全ガイド

Go 1.27 では、5 つの新しい標準ライブラリパッケージ(encoding/json/v2、encoding/json/jsontext、crypto/mldsa、uuid、simd)と 3 つの言語仕様の変更が加わりました。ジェネリックメソッドや、ほとんどの go.mod から依存関係を 1 つ減らしてくれる uuid パッケージのように、初日から使いたくなるものもあります。一方で、その機能があることに気づく前にテストスイートを失敗させるものもあります。compress/flate が出力するバイト列が変わり、関数リテラルのシンボル名が変わり、いくつかの GODEBUG の逃げ道は古い動作に戻す代わりにビルドを失敗させるようになりました。

目次

ジェネリックメソッドと、まだできない 1 つのこと

ジェネリクスが Go 1.18 で導入されて以来、型パラメータを使える場所はトップレベルの関数と型宣言の 2 つだけでした。メソッドは対象外だったので、コレクションの要素の型を変える変換を書きたければ、パッケージレベルの関数として書くしかなく、パッケージレベルの関数はチェーンできません。

おそらく次のようなコードを書いたことがあるはずです。センサーの計測値を表示用のラベルに変換するメトリクスのパイプラインには 2 つの変換が必要で、それぞれが前の呼び出しを包み込まなければなりません。

example.gogo
type Metric struct {
    Sensor  string
    Celsius int
}

type List[T any] []T

// Package-level, because a method couldn't declare U.
func Map[T, U any](l List[T], f func(T) U) List[U] {
    out := make(List[U], len(l))
    for i, v := range l {
        out[i] = f(v)
    }
    return out
}

func main() {
    metrics := List[Metric]{
        {Sensor: "cpu0", Celsius: 61},
        {Sensor: "cpu1", Celsius: 58},
    }

    temps := Map(metrics, func(m Metric) int { return m.Celsius })
    labels := Map(temps, func(c int) string { return fmt.Sprintf("%dC", c) })

    fmt.Println(labels) // [61C 58C]
}

Go 1.27 では、Map はレシーバとは独立した独自の型パラメータを宣言する、本物のメソッドになります。

example.gogo
// U is the method's own type parameter, new in Go 1.27.
func (l List[T]) Map[U any](f func(T) U) List[U] {
    out := make(List[U], len(l))
    for i, v := range l {
        out[i] = f(v)
    }
    return out
}

func main() {
    metrics := List[Metric]{
        {Sensor: "cpu0", Celsius: 61},
        {Sensor: "cpu1", Celsius: 58},
    }

    labels := metrics.
        Map(func(m Metric) int { return m.Celsius }).
        Map(func(c int) string { return fmt.Sprintf("%dC", c) })

    fmt.Println(labels) // [61C 58C]
}

わかりやすい改善はチェーンです。それほど目立たないのが命名です。レシーバがどの型を指しているかをすでに示しているので、MapSlice、MapSet、MapBox は型ごとに 1 つの Map にまとまります。レシーバはコンパイラの役にも立ちます。List[Metric] によって T が決まるので、推論が必要なのは出力の型だけです。また、メソッドはエディタでドットを入力すれば候補に表示されます。パッケージレベルのヘルパーは、存在を知っていなければ使えません。

標準ライブラリ自身もこの機能を使っています。math/rand/v2 では、パッケージレベルの N 関数と並んで、(*Rand) N[Int intType](Int) Int がメソッドとして宣言されました。以前のルールでは許されなかった形です。

ただし制限が 1 つあり、見落としがちです。リリースノートには次のように書かれています。

そのため、次のコードはコンパイルできません。

example.gogo
type Transformer interface {
    // Compile error: interface methods can't declare type parameters.
    Map[U any](f func(Metric) U) any
}

interface のディスパッチは実行時に行われるため、動的にディスパッチされる呼び出しに対して、ジェネリックメソッドのどのインスタンス化を生成すればよいかをコンパイラは知ることができません。API が interface を中心に作られているなら、変換には従来どおりパッケージレベルの関数が必要です。ジェネリックメソッドがコードベースの役に立つかどうかは、ほぼこの点で決まるので、リファクタリングを計画する前に、自分の interface を確認してください。実際に手を動かして身につけたいなら、Go Generics Masterclass で制約と型推論をブラウザ上の演習として学べます。

判定:今すぐ採用。ただし対象は具象型です。interface の形をした API には手を付けないでください。

標準ライブラリに加わった uuid パッケージ

データベースとやり取りする Go のサービスは、ほぼすべてが github.com/google/uuid をインポートしています。Go 1.27 では、プロポーザル #62026 に沿って、"uuid" というインポートパスの標準ライブラリパッケージが加わりました。

UUID は [16]byte として定義されているので、値は == で比較でき、そのまま map のキーとして使えます。ジェネレータは 3 つ用意されています。

example.gogo
package main

import (
    "fmt"
    "uuid"
)

func main() {
    fmt.Println(uuid.New())   // reach for this when you don't care how it's made
    fmt.Println(uuid.NewV4()) // 128 bits, 122 of them random
    fmt.Println(uuid.NewV7()) // 48-bit timestamp first, so values sort by creation time

    requestID, err := uuid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6")
    if err != nil {
        return
    }
    fmt.Println(requestID, requestID == uuid.Nil()) // false, it parsed fine
}

New はデフォルトの選択肢で、現在は V4 を返します。NewV4 は完全にランダムなので、次の値を誰にも推測されません。公開するリクエスト ID に求められるのはこの性質です。NewV7 は先頭に 48 ビットのタイムスタンプを置くので、新しい値ほど後ろに並びます。挿入がインデックスのあちこちではなく末尾の近くに入るので、主キーに向いています。

Nil() と Max() は関数なので、比較は括弧を付けて id == uuid.Nil() と書く点に注意してください。Nil() は「まだ設定されていない」ことを表す番兵値として使えます。Max() はすべてのビットが 1 の値で、時刻順に並ぶ V7 キーの範囲をスキャンするときの上限として役立ちます。

判定:新しいコードでは今すぐ採用。既存サービスの移行は検索と置換で済みますが、依存関係を削除する前に「よくある質問」を確認してください。

encoding/json/v2 のためにコードを変える必要はあるか?

必要ありません。それがこの設計の狙いでした。encoding/json の内部は v2 の実装になりましたが、リリースノートは「マーシャリングとアンマーシャリングの動作は維持されるが、エラーメッセージの正確な文言は変わる可能性がある」と明言しています。コードを変えずに、新しいエンジンの速さが手に入ります。リリースノートによれば、「マーシャルのパフォーマンスは以前の実装とおおむね同等で、アンマーシャルのパフォーマンスは大幅に速くなっている」とのことです。

新しい動作を選ぶには、encoding/json/v2 を名前で指定してインポートするしかありません。このパッケージのデフォルトはより厳格で、他の JSON 実装が想定する動作に合わせて選ばれています。JSON 文字列内の不正な UTF-8 を拒否し、1 つの JSON オブジェクト内での名前の重複を拒否し、フィールド名を大文字と小文字を区別して照合し、nil の slice を null ではなく [] としてマーシャルします。また、v1 では決定的な順序が保証されていた map を、非決定的な順序でマーシャルします。この nil の slice の変化は、Go の var と make の違いで取り上げた nil と空の区別によるもので、v2 ではその違いが出力で見えるようになります。

example.gogo
import "encoding/json/v2"

type Event struct {
    ID     string `json:"id"`
    Action string `json:"action"`
}

// v1 would match the key "ID" against the tag `json:"id"`. v2 won't,
// and it skips the mismatched key silently rather than erroring.
var e Event
json.Unmarshal([]byte(`{"ID":"evt_01H","action":"checkout"}`), &e)
fmt.Println(e.ID, e.Action) // "" checkout

// v2 marshals maps in a non-deterministic order. Ask for a stable one
// explicitly when you hash or snapshot the output.
inventory := map[string]int{"widget": 12, "gadget": 3, "gizmo": 7}
b, _ := json.Marshal(inventory, json.Deterministic(true))

v2 のもとで v1 の動作を取り戻したいときも、インポートを切り替える必要はありません。その動作に対応するオプションを渡します。安定した map の順序には Deterministic(true)、緩いフィールド照合には MatchCaseInsensitiveNames(true)、[] の代わりに null を出力するには FormatNilSliceAsNull(true) を使います。v1 のパッケージにも同じオプションが追加されたので、全面的に移行しなくても、v2 のセマンティクスを 1 つずつ採用できます。完全な一覧は、v1 パッケージのドキュメントの Migrating to v2 にあります。

3 つ目のパッケージ encoding/json/jsontext は低レベルの構文を扱います。出力が常に有効な JSON になるよう保つステートマシンとともに、JSON をトークンと値の並びとして公開します。ストリーミング用のコーデックはこのパッケージの上に作られます。ほとんどのアプリケーションのコードが直接インポートすることはありません。

どれをインポートするかは、次のように決めます。

flowchart TD
    A["Which JSON import?"] --> B{"Upgrading existing code?"}
    B -- Yes --> C["encoding/json<br/>v2 engine, v1 behavior"]
    B -- No --> D{"Want strict defaults<br/>and faster decoding?"}
    D -- No --> C
    D -- Yes --> E["encoding/json/v2"]
    E --> F{"Need raw tokens<br/>or streaming syntax?"}
    F -- Yes --> G["encoding/json/jsontext"]
    F -- No --> H["You're done"]

Go 1.27 で encoding/json、encoding/json/v2、encoding/json/jsontext のどれを使うかを決めるための決定木。

判定:デフォルトは今すぐ採用(すでに採用済みです)。名前を指定した v2 のインポートは後で採用しましょう。struct タグに関する移行メモを読んでからで十分です。どの程度影響を受けるかを左右するタグのルールは、Data Formats コースで扱っています。

crypto/mldsa によるポスト量子署名

RSA と ECDSA が現時点で安全なのは、大きな数の素因数分解や離散対数問題を十分な速さで解けるコンピュータが存在しないからです。十分に大規模な量子コンピュータなら、それができてしまいます。crypto/mldsa は、プロポーザル #77626 に沿って、FIPS 204 の ML-DSA を 44、65、87 の 3 つのパラメータセットで実装しています。

他のパッケージもすでにこれに対応しています。crypto/x509 は ML-DSA の鍵と署名をパースして検証でき、crypto/tls は TLS 1.3 のハンドシェイクで 3 つのパラメータセットすべてを受け付けます。量子耐性のある鍵合意のために、MLKEM1024 がサポート対象の鍵交換に加わりました。Config.CurvePreferences に追加すると有効になります。

ファームウェアのリリースに署名して検証するまでの一連の流れは次のとおりです。

example.gogo
sk, err := mldsa.GenerateKey(mldsa.MLDSA65())
if err != nil {
    return err
}

firmware, err := os.ReadFile("firmware-v2.1.0.bin")
if err != nil {
    return err
}

// Context is a domain-separation label. Sign and verify must pass the same one.
opts := &mldsa.Options{Context: "acme/firmware-release"}
sig, err := sk.Sign(nil, firmware, opts)
if err != nil {
    return err
}

fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // true
fmt.Println(mldsa.MLDSA65().SignatureSize())                         // 3309

firmware[0] ^= 1 // flip one bit
fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // false

Verify は署名が正しいときに nil を返すので、nil と比較すれば真偽値が得られます。欠点はサイズで、その差は大きなものです。

デジタル署名のサイズをバイト単位で比較した横棒グラフ。Ed25519 は 64 バイト、ECDSA P-256 は約 71 バイト、RSA-2048 は 256 バイト、ML-DSA-44 は 2,420 バイト、ML-DSA-65 は 3,309 バイト、ML-DSA-87 は 4,627 バイト。

出典:FIPS 204(ML-DSA)および各アルゴリズムの仕様に基づく署名サイズ。

ML-DSA-65 の署名は Ed25519 の署名の約 46 倍の大きさなので、使う価値があるのは署名が長く有効でなければならない場面だけです。10 年後もデバイスが信頼し続けなければならないファームウェアやリリース成果物なら、3 KB を払う価値があります。現在の暗号より長く使われることを想定した証明書のルートや、量子コンピュータが登場したときに鍵を作り直したくない TLS サーバーも同様です。一方、1 秒間に何千個も発行する短命のセッショントークンでは、増えたサイズはまだ必要のないオーバーヘッドです。

同じ領域で、これとは無関係な crypto/x509 の変更もあります。SystemCertPool が Linux だけでなく Windows と Darwin でも SSL_CERT_FILE と SSL_CERT_DIR を尊重するようになりました。これらが設定されていると、Go はディスクからルート証明書を読み込み、プラットフォームの API ではなく独自の検証器を使います。以前の動作を維持するには GODEBUG=x509sslcertoverrideplatform=0 を設定してください。

判定:長期間使う署名には採用し、それ以外では無視してかまいません。

実験的な simd パッケージ

通常の CPU 命令は 1 つの値を処理します。SIMD 命令は 1 ステップで値のベクトル全体を処理するので、同じ結果をはるかに少ない命令数で得られます。役に立つのは、音声サンプルや画像のピクセルが入った大きな slice に同じ計算を適用するときや、内積を計算するときです。主に struct や文字列を扱うコードでは効果はありません。

SIMD の公開方法はアーキテクチャごとに異なります。新しい simd パッケージはポータブルで、特定のベクトルサイズを前提としません。すべてのアーキテクチャで利用でき、ハードウェア命令が存在する場合はそれを使います。ビルド時に GOEXPERIMENT=simd を指定すると有効になります。実験的な機能なので、API は変わる可能性があります。

2 つの音声トラックを、1 ステップで複数サンプルずつミックスする例です。

example.gogo
//go:build goexperiment.simd

// out[i] = trackA[i] + trackB[i], several samples at a time.
out := make([]float32, len(trackA))

lanes := simd.LoadFloat32s(trackA).Len() // how many float32s fit in one vector
for i := 0; i+lanes <= len(trackA); i += lanes {
    va := simd.LoadFloat32s(trackA[i:])
    vb := simd.LoadFloat32s(trackB[i:])
    va.Add(vb).Store(out[i:])
}
// A plain scalar loop handles the leftover tail.

Len は、実行中のマシンで 1 つのベクトルにいくつの要素が収まるかを返すので、ループにベクトル幅をハードコードする必要はありません。各ステップでは両方のトラックからその数だけサンプルを読み込み、1 回の演算で要素ごとに加算し、結果を格納します。

判定:数値計算のホットループをすでにプロファイリングしているのでなければ、今は無視してかまいません。API は GOEXPERIMENT の背後にあり、変更される可能性があります。

アップグレードするだけで得られるパフォーマンス向上

再ビルドするだけで得られる高速化もあります。最も大きいのはサイズ別に特化したアロケーションで、リリースノートでは次のように説明されています。

Go 1.27 のサイズ特化型アロケータを示すグラフ。80 バイト未満のアロケーションは最大 30% 安くなり、アロケーションの多い実際のプログラムは全体で約 1% 改善し、バイナリサイズは一定の 60 KB 増加する。

出典:Go 1.27 リリースノート、メモリアロケーションの高速化。

この 2 つの数字は別のものを測っています。30% は小さなアロケーション 1 回の呼び出しにかかるコストで、約 1% はサービス全体で見える改善です。性能が低下した場合は GOEXPERIMENT=nosizespecializedmalloc で無効にできますが、この逃げ道は Go 1.28 で削除される予定です。

また、3 つのコンパイラ最適化がデフォルトで有効になっています。known-bits データフロー解析は、値のどのビットが 0 または 1 に確定しているかを追跡し、それによって生じる冗長な処理を取り除きます。ループ不変式の移動(loop-invariant code motion)は、結果が変わらない計算をループ本体の外に出し、反復のたびではなく 1 回だけ実行されるようにします。そして switch 文は、case の構成が許す場合は fallthrough を使っていてもルックアップテーブルにコンパイルされるようになり、各 case を順に判定する代わりに、一致する case へ直接ジャンプします。

compress/flate も速くなりましたが、注意点が 1 つあります。エンコードされた出力が Go 1.26 と完全には一致しない可能性があります。DEFLATE は archive/zip、compress/gzip、compress/zlib、image/png の土台になっているため、これらのどれかをバイト単位で比較するゴールデンテストは失敗することがあります。出力は正しいままなので、フィクスチャを更新してください。

判定:今すぐ採用。どちらにしても、アップグレードすれば適用されます。

goroutine リークの検出とラベル付きトレースバック

goroutineleak プロファイルが実験段階を終えて正式に利用可能になり、GOEXPERIMENT の goroutineleakprofile は削除されました。runtime/pprof から利用できるほか、net/http/pprof のエンドポイント /debug/pprof/goroutineleak としても公開されています。

リークした goroutine とは、決して解除されることのない並行処理プリミティブでブロックしている goroutine のことです。ランタイムはガベージコレクタを使ってこれを見つけます。goroutine G がプリミティブ P でブロックしていて、実行可能な goroutine からも、それらがブロック解除しうるものからも P に到達できない場合、G が再び起きることはありません。

example.gogo
func startJob() {
    result := make(chan int) // unbuffered: the send waits for a receiver
    go func() {
        result <- expensiveWork() // blocks forever, nobody receives
    }()
    // returns without receiving, so result becomes unreachable
}

func main() {
    startJob()
    runtime.GC() // the detector scans during a GC cycle
    pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
    // goroutineleak profile: total 1
    //   ... main.startJob.func1 ... main.go:14
}

この方法には盲点があります。ブロックの原因となっているプリミティブが、グローバル変数や実行可能な goroutine のローカル変数から到達可能な場合、ランタイムはリークを見逃すことがあります。パッケージレベルのレジストリに保存された channel は到達可能と見なされるので、プロファイルには報告されません。こうしたリークを生むパターンと、context を使ってそれを防ぐ方法は、Concurrency Fundamentals で順を追って解説しています。

2 つ目のデバッグ面の改善は、コードを一切必要としません。go ディレクティブが 1.27 以降のモジュールでは、トレースバックのヘッダー行に runtime/pprof の goroutine ラベルが含まれるようになりました。プロファイリング用にすでに設定しているラベルが、クラッシュダンプ、SIGQUIT のトレース、runtime.Stack の出力に表示されます。2 つの goroutine のスタックが同じとき、両者を見分ける手段がラベルしかないこともよくあります。

example.gogo
pprof.Do(ctx, pprof.Labels("request", id), func(ctx context.Context) {
    work()
})

// goroutine 34 [running]:
// labels: {"request":"req-42"}
// main.handle.func1 ...

クラッシュダンプに含めたくない情報をラベルに入れている場合は、GODEBUG=tracebacklabels=0 を設定して無効にしてください。

判定:今すぐ採用。どちらもコストはかかりません。

言語とライブラリの細かな改善

struct リテラルでのフィールドセレクタ

struct リテラルのキーに、トップレベルのフィールド名だけでなく、その型で有効な任意のフィールドセレクタを書けるようになりました(プロポーザル #9859)。共通のモデルを埋め込んでいても、入れ子のリテラルを書き出す必要はもうありません。

example.gogo
type Model struct {
    ID        int64
    CreatedAt time.Time
}

type Post struct {
    Model
    Author string
    Likes  int
}

p := Post{Model: Model{ID: 42}, Author: "Patrik"} // before
p := Post{ID: 42, Author: "Patrik"}               // Go 1.27

関数の型推論の一般化

ジェネリック関数を、対応する関数型に代入または変換するあらゆる場面で、関数の型推論が働くようになりました(プロポーザル #77245)。以前は、型を明示した変数宣言でしか推論されませんでした。

example.gogo
func ascending[T cmp.Ordered](a, b T) int  { return cmp.Compare(a, b) }
func descending[T cmp.Ordered](a, b T) int { return cmp.Compare(b, a) }

orders := []func(int, int) int{ascending[int], descending[int]} // before
orders := []func(int, int) int{ascending, descending}           // Go 1.27

slice の要素型は func(int, int) int なので、Go は T を int と推論します。同じことが、変換や、ジェネリック関数をそのまま引数として渡す場合にも使えるようになりました。

strings.CutLast と bytes.CutLast

Cut は最初の区切り文字で分割します。CutLast は最後の区切り文字で分割し、その前の部分、後の部分、区切り文字が見つかったかどうかを返します(プロポーザル #71151)。

example.gogo
dir, file, found := strings.CutLast("internal/store/user.go", "/")
fmt.Println(dir, file, found) // internal/store user.go true

以前は LastIndex を呼び、-1 かどうかを確認してから、手作業で 2 回スライスしていました。

Rand.N、maphash.Hasher、big.Int.Divide

math/rand/v2 にジェネリックメソッドとして Rand.N が加わりました(プロポーザル #77853)。グローバルな乱数源ではなく、自分でシードを与えた乱数源から、範囲を限定した乱数を取り出せます。グローバルな乱数源にはシードを指定できません。これで、失敗したテストを同じ入力で再現できます。

hash/maphash には、独自の map や Bloom フィルタのようなハッシュベースのデータ構造のための、ジェネリックな Hasher[T] interface が加わりました(プロポーザル #70471)。ハッシュ関数と等価性のチェックを組にしたもので、等しい値は同じハッシュ値にならなければなりません。ComparableHasher[T] は、comparable な型向けに == で比較する既製の実装です。go/types はすでにこれを使っており、Identical の関係に従う Hasher を提供しています。

math/big.Int には Divide メソッドが加わりました。商と余りを、Trunc、Floor、Round、Ceil から選んだ丸めモードでまとめて計算します。Quo と Mod は常にゼロ方向に切り捨てるので、別の丸め方が必要な金融や数値計算のコードでは、組み込みの選択肢ができたことになります。

Unicode 17、database/sql、その他の変更

unicode パッケージとその上に作られたすべてが、Unicode 15 から一気に Unicode 17 に移行しました。そのため、間の 2 つのリリースで追加された文字も正しく分類されるようになりました。database/sql には ConvertAssign が加わりました。ドライバは独自に実装しなくても、Rows.Scan が行う型変換を再利用できます。

残りは小さな変更です。

  • crypto に、external-mu 方式の ML-DSA 署名を指示するための仕組みとして、ハッシュ値 MLDSAMu が加わりました。
  • crypto/ecdsa は、nil でない SignerOpts を渡したとき、PrivateKey.Sign でハッシュの長さが正しいかを確認するようになりました。
  • crypto/x509 は Certificate、CertificateRequest、RevocationList で RawSignatureAlgorithm を公開します。SignatureAlgorithm が UnknownSignatureAlgorithm の場合でも、DER エンコードされた AlgorithmIdentifier を取得できます。pkix.Name へのパースも、より幅広い値の型を受け付けるようになり、未知の型は asn1.RawValue に格納されます。
  • crypto/tls には、ピアに提示した証明書チェーンを表す ConnectionState.LocalCertificate と、QUICConfig.ClientHelloInfoConn が加わりました。Config.Rand は非推奨になり、決定的なテストには代わりに testing/cryptotest.SetGlobalRandom を使います。
  • net では、UnixConn の読み取りメソッドが io.EOF を net.OpError で包まず、そのまま返すようになりました。
  • runtime/secret は、secret モードの中で作成された goroutine にもそのモードを引き継ぎます。
  • go/constant に StringLen が、go/scanner に Scanner.End が加わり、go/token の File には String メソッドが追加されました。
  • ポートについては、Linux 上のビッグエンディアン 64 ビット PowerPC ポートが ELFv2 システム ABI に移行しました。これにより、この環境で cgo、位置独立実行形式、外部リンクが使えるようになり、Linux カーネル 3.13 以降が必要になります。Plan 9 では syscall.Errno が定義され、error を実装するようになりました。そのため、これを参照するポータブルなコードがビルド制約なしでビルドできます。また、リンカが -macos と -macsdk を受け付けるようになり、macOS の LC_BUILD_VERSION ロードコマンドに書き込まれるバージョンを指定できます。

Go のツールチェーンでは何が変わったのか?

go fix に 4 つのモダナイザーが加わりました。embedlit は、複合リテラル内の埋め込みフィールドへの参照を簡潔にします。新しい struct リテラルのルールで可能になった書き換えです。atomictypes は sync/atomic の呼び出しで使われている基本型を atomic 型に置き換え、slicesbackward は逆順のループを slices.Backward に書き換え、unsafefuncs は unsafe なポインタ演算を関数呼び出しに置き換えます。あわせて整理のための変更も 2 つあります。fmtappendf はスタイル上の懸念から削除され、waitgroup は waitgroupgo に名前が変わりました。go ディレクティブを上げたら、一度実行してください。

example.bashbash
go fix -diff ./...   # preview
go fix ./...         # apply

go fix による書き換えの全一覧は、go fix 1.26 に含まれる全モダナイザーをご覧ください。

go test は、vet の stdversion チェックをデフォルトで実行するようになりました。このチェックは、go.mod のディレクティブとビルドタグで決まるそのファイルの Go バージョンよりも新しい標準ライブラリのシンボルを報告します。モジュールが go 1.25 を宣言しているのに誰かが strings.CutLast を使った場合、1.25 を使っているユーザーが気づく前に、テストの実行で検出されます。また、go test -json は "Action":"output" の行に、省略可能な "OutputType" フィールドを付けるようになりました。現在の値は error、error-continue、frame のいずれかで、CI でテスト出力をパースしている場合に役立ちます。

go doc が package@version 構文を受け付けるようになりました。go doc rsc.io/[email protected] のように書けば、チェックアウトしなくても特定のリリースのドキュメントを読めます。新しい -ex フラグはパッケージの実行可能な example を一覧表示し、名前を指定するとそのソースを表示します。

go mod tidy は、go 1.27 以降のモジュールに 2 ブロック構成を強制し、散らばった require ブロックを、直接依存の 1 ブロックと間接依存の 1 ブロックにまとめます。依存関係に付いたコメントブロックは保持され、直接依存と間接依存が混在する範囲にかかるコメントは直接依存のブロックに移動します。

go tool trace -http=:6060 は、go tool pprof と同じく localhost にだけバインドするようになりました。別のマシンからアクセスする必要がある場合は、-http=0.0.0.0:6060 のように完全なアドレスを指定してください。また、compile、link、asm、cgo、cover、pack の各ツールが、GCC 互換形式のレスポンスファイル(@file)を受け付けるようになりました。コマンドラインの長さ制限を超えるビルドシステムで役立ちます。

テストと net/http

httptest.NewTestServer は、testing/synctest と組み合わせて使うために作られたインメモリの疑似ネットワーク上に Server を作成します。実際の TCP ポートは使いません。対になるのが synctest.Sleep で、time.Sleep と synctest.Wait を 1 回の呼び出しで行います。疑似的な時計を進めてから、goroutine が落ち着くのを待つわけです。この 2 つを使えば、HTTP テストがタイミングに左右されなくなります。これらの土台になっている synctest のモデルは、Professional Go Testing で扱っています。

example.gogo
srv := httptest.NewTestServer(t, handler) // in-memory, cleanup auto-registered

net/http 側では、HTTP/2 サーバーが RFC 9218 で定義されたクライアントの優先度シグナルを受け付け、優先度の高いストリームから処理するようになりました。以前のラウンドロビンの動作に戻すには、Server.DisableClientPriority = true を設定します。

最も気づきやすいのは HTTP/1 の変更です。途中まで読んだ Response.Body を閉じると、接続を再利用できるように、残りの内容を控えめな上限まで読み捨てるようになりました。ほとんどのプログラムでは何も変わらないか、少し速くなる程度です。大きなダウンロードを中断するために早めに閉じている場合は、Transport.DisableKeepAlives = true でこの動作を無効にできます。

Server.MaxHeaderValueCount は、1 つのヘッダーが持てる値の数に上限を設け、ヘッダーに大量の値を詰め込むリクエストを防ぎます。Transport と Server は、自分で用意した net.Conn でも、ConnectionState() tls.ConnectionState を実装していれば TLS ALPN のネゴシエーションを行えます。そのため、トンネルやプロキシを経由した接続でも HTTP/2 が使われます。さらに net/url には、ディープコピー用の URL.Clone と Values.Clone が加わりました。これらの土台になるサーバーとトランスポートの設定は、HTTP and Networking で扱っています。

判定:今すぐ採用。不安定な HTTP テストスイートを抱えているなら、httptest と synctest の組み合わせだけでもアップグレードする理由になります。

Go 1.27 へのアップグレードで何が壊れるのか?

リリースノートでは、互換性を壊す変更が 8 つのセクションに分かれています。この表では、それらを遭遇しやすい順にまとめました。

何が変わるか影響を受けるもの対処法
compress/flate が出力するバイト列が変わるgzip、zlib、zip、png の出力をバイト単位で比較するゴールデンテストフィクスチャを更新してください。圧縮結果は正しく、バイト列が違うだけです。
関数リテラル(クロージャ)の名前が簡素になるシンボル名をアサートするテストや、関数のコードポインタの等価性を比較するコードリテラルの名前に依存しないようにしてください。関数のポインタ比較は、以前から信頼できないと文書化されています。
GODEBUG の asynctimerchan と gotypesalias が完全に削除されるgo.mod や //go:debug 行でこれらを古い値に固定しているものアップグレード前に grep -r asynctimerchan . を実行してください。最終的なデフォルト値を設定している場合はビルドできますが、古い値を設定しているとビルドに失敗します。
TLS/x509 の GODEBUG が 5 つ削除される:tlsunsafeekm、tlsrsakex、tls3des、tls10server、x509keypairleaf古い TLS の動作に固定したままのサービス上と同じルールです。go コマンドは最終的なデフォルト値を受け付け、古い値を拒否するので、ビルド時に見つかります。
macOS 13 Ventura が最小要件になる古い macOS で動いている CI ランナーや開発マシンランナーのイメージを更新してください。Go 1.26 のリリースノートで予告されていました。
go コマンドから bzr のサポートが削除されるBazaar サーバーでホストされているモジュール依存関係をミラーするか、vendor に取り込んでください。
新しい //go:linknamestd ディレクティブが標準ライブラリ専用の linkname を示し、リンカがアセンブリシンボルへの linkname アクセスを検査するようになり、型記述子が .go.type セクションに移動した自分のコードか依存関係かを問わず、認められていない //go:linkname でランタイムの内部にアクセスしているパッケージ依存関係を更新してください。これらはビルド時にはっきりと失敗します。どれもリリースノートには載っていないので、ビルドして初めて気づくことになります。
HTTP/1 の Response.Body.Close が未読の内容を読み捨てる大きなダウンロードを中断するために早めに閉じるコードそうしたクライアントでは Transport.DisableKeepAlives = true を設定してください。
json/v2 のエラーメッセージの文言が変わるJSON のエラー文字列を完全一致でアサートするテストメッセージ全体ではなく、エラーの型や部分文字列で照合してください。

最も見つけにくいのは最後の行です。動作は維持されても文言は維持されないので、アンマーシャルのエラーを文字列で照合しているテストスイートは、はっきりした原因が見えないまま失敗します。go.mod をすでに go 1.27 に上げたブランチで 2 回実行して、原因を切り分けましょう。

example.bashbash
go test ./... > default.txt 2>&1
GOEXPERIMENT=nojsonv2 go test ./... > nojsonv2.txt 2>&1
diff default.txt nojsonv2.txt

差分に出てくるものは、JSON の変更によるものです。両方の実行で失敗するものは、表にある別の原因によるものです。GOEXPERIMENT=nojsonv2 は将来のリリースで削除される予定なので、原因の診断に使い、そのあとでテストを修正してください。

よくある質問

Go 1.27 に合わせて JSON のコードを書き直す必要はありますか?

いいえ。encoding/json パッケージは v2 の上に実装されるようになりましたが、マーシャリングとアンマーシャリングの動作は維持され、v1 の API も引き続きサポートされます。外から見える違いは、エラーメッセージの正確な文言が変わる可能性があることだけです。より厳格な v2 のデフォルトを使うには、encoding/json/v2 を明示的にインポートする必要があります。

ジェネリックメソッドで interface を満たせますか?

いいえ。リリースノートには、interface のメソッドは型パラメータを宣言できず、interface のメソッドをジェネリックメソッドで実装することもできないと書かれています。interface のディスパッチは実行時に解決されるため、動的にディスパッチされる呼び出しに対してどのインスタンス化を生成すればよいかを、コンパイラは知ることができません。API が interface を中心に作られているなら、引き続きパッケージレベルのジェネリック関数を使ってください。

github.com/google/uuid をやめて標準ライブラリのパッケージに移行すべきですか?

新しいコードでは、移行すべきです。既存のコードでは、先に 2 つのことを確認してください。標準ライブラリの UUID は独自のメソッドセットを持つ [16]byte なので、サードパーティの型にしかないメソッドを使っているコードは見直しが必要です。また、API でサードパーティの uuid.UUID を公開している依存関係があれば、どちらにしてもそのモジュールは依存グラフに残ります。

crypto/mldsa は本番環境で使えますか?

FIPS 204 を実装した安定版の標準ライブラリパッケージで、crypto/x509 と crypto/tls にも組み込まれています。実用上の制約は成熟度ではなく署名のサイズです。ML-DSA-65 の署名は 3,309 バイトで、Ed25519 は 64 バイトです。署名が現在の暗号より長く有効でなければならない場面で使い、大量に発行する短命のトークンには使わないでください。

Go 1.27 で自分のコードベースの何が壊れるかを、最も早く調べるにはどうすればよいですか?

ブランチで go.mod を go 1.27 に上げてから、asynctimerchan、gotypesalias、削除された 5 つの TLS の設定を grep で探します。これらはビルドそのものを失敗させるからです。次に、テストスイートを通常どおり 1 回、GOEXPERIMENT=nojsonv2 を付けて 1 回実行し、失敗の差分を取ります。これで、JSON のエラー文言の変化による失敗と、ゴールデンファイルやシンボル名による失敗を切り分けられます。

出典

この記事で引用した一次情報源は次のとおりです(最終確認日:2026 年 8 月 23 日)。

次のステップ

ジェネリックメソッドは、制約と型推論に慣れてからのほうがうまく使えます。そしてこの 2 つは、ジェネリクスを初めて学ぶときに流し読みされがちな部分です。Go Generics Masterclass では、制約、型セット、型推論を、最新のツールチェーンで動くブラウザ上の演習として学べます。1.27 のメソッドの構文を実際のコードで試せます。

Go を始めたばかりなら、まず Go Fundamentals トラックから始めて、それからリリースノートに戻ってきてください。また、昨年のリリースをまだ確認していないなら、Go 1.26 の新機能で errors.AsType、Green Tea ガベージコレクタ、new(expr) を解説しています。

シニアエンジニアのように Go を書く

ブラウザで学べるインタラクティブなレッスン。最初のレッスンは無料です。

無料レッスンを試すまたは無料アカウントを作成