標準ライブラリに UUID の生成・パースを担う uuid パッケージが新設された。
New uuid package
概要
標準ライブラリに新しい uuid パッケージが追加され、RFC 9562 に準拠した UUID(Universally Unique Identifier)の生成とパースがサードパーティ依存なしに行えるようになった。中心となる UUID 型は [16]byte を基底型とし、汎用的な生成関数 New(現時点では NewV4 と同等)、バージョン別の生成関数 NewV4・NewV7、パース関数 Parse・MustParse、特殊値を返す Nil・Max、テキスト変換用の String・MarshalText・AppendText・UnmarshalText、比較用の Compare を提供する。乱数部分は暗号論的に安全な乱数生成器で生成される。あわせて database/sql も UUID 型を認識するようになり、ドライバが独自の変換処理を持たない場合は自動的に文字列表現との相互変換が行われる。
導入経緯
Issue #62026 として提案された。github.com/google/uuid が事実上の標準として広く使われてきたが、UUID対応は2018年(#23789)・2019年(#28324)にも提案されながら当時は見送られていた。今回はエコシステムの利用実態(どの関数がどれだけ使われているか)の分析結果を踏まえて再提案され、最小限のAPIに絞ることで合意形成が進んだ。5ヶ月あまり・300件を超えるコメントを経て議論がまとまり、最終的にacceptedとなった。
議論タイムライン
- 2026-01-28: Proposal Review Meetingで「Active」ステータスに変更。
- 2026-02-25:
aclementsが「likely accept」を宣言。Parseが受け付けるフォーマットの拡充、Nil/Maxの扱い、Compareのドキュメント改善などを含む最終API仕様が固まる。 - 2026-04-08:
aclementsがコンセンサスに変化がないことを確認し、正式に「accepted」と宣言。
議論のハイライト
- パッケージ名は当初
crypto/uuidとして提案されたが、「暗号」という語が強すぎる印象を与えることと、UUIDを探すユーザーはcrypto/配下を見に行かないという実用上の理由から、最終的に単純なuuidに変更された。 New()(バージョンを指定しない汎用生成関数)を含めるかどうかが最も議論を呼んだ。github.com/google/uuidのエコシステム分析で生成関数呼び出しの大半がV4相当であることが示され、「バージョンを気にしないユーザー向けの明確なデフォルト」として採用に至った。Nil・Maxをvar(github.com/google/uuid互換)にするかfuncにするかで議論があった。varはuuid.Nil[0] = 0xffのように書き換え可能であるのに対し、func Nil() UUIDはその危険がないため、ゼロから設計するならfuncが自然という結論になった。Parseが受け付ける文字列形式について、RFC 9562の正規形(ハイフン区切り)のみを受け付ける厳格案と、github.com/google/uuidとの実行時互換性を優先する案が対立し、最終的には波括弧形式・URN形式・ハイフンなし形式も含めて受け付ける方向でまとまった。- UUIDの内部構造(バージョンやタイムスタンプなど)を取り出すメソッドは意図的に除外された。RFC 9562がUUIDを不透明な値として扱うことを推奨していることと、エコシステム分析でそのようなメソッドの利用率が極めて低かったことが理由。
使用例
Before
package main
import (
"crypto/rand"
"fmt"
)
// 標準ライブラリに UUID 型がなかったため、
// バージョン4 UUIDを自前で組み立てる必要があった
func newV4() string {
var b [16]byte
rand.Read(b[:])
b[6] = (b[6] & 0x0f) | 0x40 // version 4
b[8] = (b[8] & 0x3f) | 0x80 // variant 10
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
func main() {
fmt.Println(newV4())
}
After
package main
import (
"fmt"
"uuid"
)
func main() {
id := uuid.New() // 現時点では NewV4 相当
fmt.Println(id)
id7 := uuid.NewV7() // タイムスタンプを含む単調増加UUID
fmt.Println(id7)
parsed, err := uuid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6")
if err != nil {
fmt.Println("parse error:", err)
return
}
fmt.Println(parsed == uuid.Nil())
}
移行時の注意
New() は現時点では NewV4 と同等だが、将来別のアルゴリズムに変更される可能性がドキュメント上明記されている。特定のバージョンが必要な場合は NewV4・NewV7 を明示的に使うこと。UUID の基底型は github.com/google/uuid と同じ [16]byte なので型変換自体は容易だが、Parse の挙動(受け付ける文字列形式)や New がエラーを返さない点など細部の差異があるため、既存コードをそのまま置き換える際はAPIの違いを確認する必要がある。
実装解説
UUID 型は単純な [16]byte として定義されている(uuid.go:33)。
Parse/UnmarshalText は、入力バイト列の長さで4つの形式(標準形式、波括弧形式、URN形式、ハイフンなし形式)を判別してから共通のハイフン付き16進デコード処理に合流する(uuid.go:124-166)。
NewV4 は crypto/rand.Read で全16バイトを乱数で埋めたあと、バージョン・バリアントの各ビットフィールドだけを上書きする(uuid.go:189-195)。
NewV7 はパッケージレベルの sync.Mutex(v7mu)と直前に生成したタイムスタンプ(v7lastSecs/v7lastTimestamp)を保持し、同一ミリ秒内で連続生成された場合はタイムスタンプを1/4096ミリ秒ずつ繰り上げ、システムクロックが巻き戻った場合はその繰り上げをリセットすることで、単調増加を保証している(uuid.go:197-265)。
database/sql 側では、convert.go の変換処理が string/[]byte から *uuid.UUID への代入時に uuid.Parse/UnmarshalText を呼び出し(convert.go:270, convert.go:307)、逆方向は database/sql/driver の defaultConverter.ConvertValue が uuid.UUID を driver.Value として渡す際に String() で文字列化する(types.go:262)。これにより、UUID対応を持たないドライバでも UUID 型のカラムを文字列として透過的に扱える。