メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

新機能

標準ライブラリに UUID の生成・パースを担う uuid パッケージが新設された。

New uuid package

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

標準ライブラリに新しい uuid パッケージが追加され、RFC 9562 に準拠した UUID(Universally Unique Identifier)の生成とパースがサードパーティ依存なしに行えるようになった。中心となる UUID 型は [16]byte を基底型とし、汎用的な生成関数 New(現時点では NewV4 と同等)、バージョン別の生成関数 NewV4NewV7、パース関数 ParseMustParse、特殊値を返す NilMax、テキスト変換用の StringMarshalTextAppendTextUnmarshalText、比較用の Compare を提供する。乱数部分は暗号論的に安全な乱数生成器で生成される。あわせて database/sqlUUID 型を認識するようになり、ドライバが独自の変換処理を持たない場合は自動的に文字列表現との相互変換が行われる。

導入経緯

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相当であることが示され、「バージョンを気にしないユーザー向けの明確なデフォルト」として採用に至った。
  • NilMaxvargithub.com/google/uuid 互換)にするか func にするかで議論があった。varuuid.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 と同等だが、将来別のアルゴリズムに変更される可能性がドキュメント上明記されている。特定のバージョンが必要な場合は NewV4NewV7 を明示的に使うこと。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)。

NewV4crypto/rand.Read で全16バイトを乱数で埋めたあと、バージョン・バリアントの各ビットフィールドだけを上書きする(uuid.go:189-195)。

NewV7 はパッケージレベルの sync.Mutexv7mu)と直前に生成したタイムスタンプ(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/driverdefaultConverter.ConvertValueuuid.UUIDdriver.Value として渡す際に String() で文字列化する(types.go:262)。これにより、UUID対応を持たないドライバでも UUID 型のカラムを文字列として透過的に扱える。

関連リンク