🧪
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