🧪

Go 1.25 を“手で動かして”理解する

に公開

Go 1.25 の新機能のうち、標準ライブラリで“手触りがある”ものを macOS (Apple Silicon) で実行しながら学べるようまとめました。本文のコードと手元の実行ログはすべて Go 1.25 で動作確認済みです。

  • testing/synctest … 並行処理の時間を仮想化してテスト
  • sync.WaitGroup.Go … Add/Done を書かずに安全に goroutine 起動
  • net/http.CrossOriginProtection … CSRF 保護を標準で簡単に
  • encoding/json/v2(オプトイン) … 柔軟なタグ&オプション、高速化
  • reflect.TypeAssert … interface{}経由せずアロケーション無しで取り出し

この記事のコードは Go 1.25 を使用。JSON v2 のデモは
GOEXPERIMENT=jsonv2 を有効にして実行します。

ひな形(main.go / main_test.go)

まずは全文。あとで各機能を分解して解説します。

main.go

package main

import (
	"encoding/json/jsontext"
	"encoding/json/v2"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"sync"
	"time"
)

// 60 秒以内にチャネルから読み取る関数 (タイムアウト時はエラー)
func Read(ch <-chan int) (int, error) {
	select {
	case v := <-ch:
		return v, nil
	case <-time.After(60 * time.Second):
		return 0, errors.New("timeout")
	}
}

func oldWaitingGoRoutine() {
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		// 何らかの処理1
	}()

	go func() {
		defer wg.Done()
		// 何らかの処理2
	}()

	wg.Wait()
	fmt.Println("完了")
}

func newWaitingGoRoutine() {
	var wg sync.WaitGroup

	wg.Go(func() {
		// 何らかの処理1
	})

	wg.Go(func() {
		// 何らかの処理2
	})

	wg.Wait()
	fmt.Println("完了")
}

func serve() {
	mux := http.NewServeMux()
	mux.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
		// 更新処理 (状態変更を伴う操作)
		fmt.Println(w, "Updated!")
	})

	cop := http.NewCrossOriginProtection()

	// 信頼するオリジンを登録 (ここからのリクエストは許可)
	cop.AddTrustedOrigin("https://example.com")
	cop.AddTrustedOrigin("https://*.example.com") // ワイルドカード指定も可能

	// サーバ起動時にCrossOriginProtectionをハンドラに適用
	http.ListenAndServe(":8080", cop.Handler(mux))
}

type Address struct {
	City string `json:"city"`
}
type Person struct {
	Name      string          `json:"name"`
	BirthDate time.Time       `json:"birth_date,format:DateOnly"` // 日付のみのフォーマット
	Address   `json:",inline"` // Address のフィールドをフラット化
	Extra     map[string]any  `json:",unknown"` // 不明なフィールド受け取り
}

func main() {
	// 入力JSON (Extra に "hobby" フィールドなど Person にないものを含む)

	src := []byte(`{
		"name": "Alice",
		"birth_date": "2001-07-15",
		"street": "123 Main St",
		"city": "Wonderland",
		"hobby": "Adventuring",
		"friends": [{"name": "Bob"}, {"name": "Cindy"}]
 	}`)

	var p Person
	if err := json.Unmarshal(src, &p); err != nil {
		panic(err)
	}
	fmt.Printf("Name: %s, BirthDate: %s, City: %s\n", p.Name, p.BirthDate.Format("2006-01-02"), p.City)
	fmt.Println("Extra:", p.Extra) // Extra: map[hobby:Chess]

	// 整形された JSON 文字列として出力(インデントオプション利用)
	out, _ := json.Marshal(p, jsontext.WithIndent("    "))
	fmt.Println(string(out))

	rv := reflect.ValueOf(42)
	v, ok := reflect.TypeAssert[int](rv)
	fmt.Println(v, ok) // 出力: 42 true
}

main_test.go

package main

import (
	"testing"
	"testing/synctest"
)

func TestReadTimeout(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		ch := make(chan int)
		_, err := Read(ch)
		if err == nil {
			t.Fatal("expected timeout error, got nil")
		}
	})
}

1. testing/synctest:時間を“進めて”テストする

Read は 60 秒待ってからタイムアウトする関数。普通にテストすると 60 秒かかりますが、synctest.Test 内ではバーチャルクロックが動くため 即座に タイムアウト分岐を検証できます。

% GOEXPERIMENT=jsonv2 go test
# 出力
PASS
ok  	example.com	0.246s
  • synctest.Test(t, func(t *testing.T){ ... }) 内では、time.After などの時間APIが仮想時間を使う
  • テストは決定的かつ高速。タイムアウト/リトライ系ロジックの信頼性が上がります

2. sync.WaitGroup.Go:Add/Done を省いてミスを無くす

同じことを、古い書き方と新しい書き方で比較:

// 旧: Add/Done が散らばる
wg.Add(2)
go func(){ defer wg.Done(); /*...*/ }()
go func(){ defer wg.Done(); /*...*/ }()
wg.Wait()

// 新: Go メソッドに渡すだけ
wg.Go(func(){ /*...*/ })
wg.Go(func(){ /*...*/ })
wg.Wait()
  • wg.Go は内部で Add(1) → 別 goroutine で実行 → 終了時に自動 Done()
  • カウント漏れやdefer 忘れを防げます

3. net/http.CrossOriginProtection:CSRF を標準で防ぐ

最小コードは次の通り(serve()):

mux := http.NewServeMux()
mux.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Updated!")
})

cop := http.NewCrossOriginProtection()
cop.AddTrustedOrigin("https://example.com")
cop.AddTrustedOrigin("https://*.example.com")

http.ListenAndServe(":8080", cop.Handler(mux))
  • 他オリジンからの 非セーフメソッド(POST/PUT/DELETE…) を既定で 403 に
  • 許可したいオリジンは AddTrustedOrigin でホワイトリスト化

4. encoding/json/v2(実験):“タグとオプション”が超便利

本記事の Person は以下のポイントを使っています:

  • birth_date,format:DateOnly … time.Time に日付のみの書式
  • ,inline … 埋め込み構造体をフラット化
    ※ 落とし穴:json:"inline" と書くと名前が “inline” のフィールドになってしまいます。正しくは json:",inline"。
  • ,unknown … 構造体に無いフィールドを Extra マップへ自動退避
  • jsontext.WithIndent(" ") … 整形出力

実行ログ(手元)

% GOEXPERIMENT=jsonv2 go run ./main.go
Name: Alice, BirthDate: 2001-07-15, City: Wonderland
Extra: map[hobby:Adventuring friends:[map[name:Bob] map[name:Cindy]] street:123 Main St]
{
    "name": "Alice",
    "birth_date": "2001-07-15",
    "city": "Wonderland",
    "hobby": "Adventuring",
    "friends": [
        {"name": "Bob"},
        {"name": "Cindy"}
    ],
    "street": "123 Main St"
}
42 true

5. reflect.TypeAssert:アロケーション無しの取り出し

rv := reflect.ValueOf(42)
v, ok := reflect.TypeAssert[int](rv)
fmt.Println(v, ok) // 出力: 42 true
  • 旧来の rv.Interface().(T) と違い、interface{} を経由しないため割り当てが発生しません
  • リフレクション多用箇所のパフォーマンス最適化に有効

6. 使いどころの指針

  • synctest:タイムアウト・リトライ・レート制限など時間依存の並行処理テスト
  • WaitGroup.Go:goroutine 多発の場面で可読性と安全性を両立
  • CrossOriginProtection:まずは標準のCSRF保護をオン。例外はホワイトリストで調整
  • JSON v2:埋め込みの整形、未知フィールドの収集、独自フォーマットなど実務の柔軟性を一気に底上げ
  • TypeAssert:リフレクションのホットパスを最適化

Discussion