Swift Digest

RandomNumberGenerator を使って UUID を生成する

Generating UUIDs using RandomNumberGenerators

Proposal
SF-0031
Authors
FranzBusch
Status
Accepted & Implemented

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

UUID(Universally Unique IDentifier)は 128 ビットの識別子で、空間的・時間的にユニークであることを目的としたものです。Foundation には、ランダムな UUID を生成するためのイニシャライザ UUID() が用意されており、多くのアプリケーションではこれをそのまま呼び出して新しい UUID を作っていました。

しかし、UUID() イニシャライザはランダムさの源(ソース)を切り替える手段を提供していません。Swift の標準ライブラリには、乱数生成器の共通の抽象として RandomNumberGenerator プロトコルがあり、Int.random(in:using:) のようにこのプロトコルを通じてランダム値の生成元を差し替えられる API が用意されています。一方で UUID だけは、システム既定の生成器以外を使うことができませんでした。

このため、たとえばテストや再現性のあるシミュレーションのように、決定的にシードされた RandomNumberGenerator から UUID を生成したいケースでは、自前で 16 バイトのランダムなバイト列を作って UUID(uuid:) に渡すなどのコードを書く必要があり、UUID v4 としての形式(バージョンビットやバリアントビットの設定)を自分で守らなければならないという手間がありました。

02 どのように解決されるのか

UUID に、任意の RandomNumberGenerator からランダムな UUID を生成する静的メソッド random(using:) が追加されます。

extension UUID {
    /// Generates a new random UUID.
    ///
    /// - Parameter generator: The random number generator to use when creating the new random value.
    /// - Returns: A random UUID.
    @available(FoundationPreview 6.3, *)
    public static func random(
        using generator: inout some RandomNumberGenerator
    ) -> UUID
}

引数の generatorinout で受け取るため、Int.random(in:using:) などと同じ呼び出し方で使えます。たとえば、決定的にシードされた独自の乱数生成器を用意しておけば、テストごとに毎回同じ UUID 列を再現できます。

struct SeededRNG: RandomNumberGenerator {
    var state: UInt64
    mutating func next() -> UInt64 {
        state = state &* 6364136223846793005 &+ 1442695040888963407
        return state
    }
}

var rng = SeededRNG(state: 42)
let id1 = UUID.random(using: &rng)
let id2 = UUID.random(using: &rng)
// rng の状態が同じなら、id1 / id2 も毎回同じ値になる

既存の UUID() イニシャライザはこれまでどおりシステム既定の乱数生成器を使ってランダムな UUID を生成します。今回追加されるのは静的メソッドの random(using:) のみで、追加 API のため既存コードへの影響はありません。利用には FoundationPreview 6.3 以降が必要です。

なお、命名としてはイニシャライザ形式の UUID(using: &rng) も候補に挙がりましたが、Int.random(in:) のように標準ライブラリの他の型で random という静的メソッドが定着していることから、それに合わせて静的メソッドとして提供されます。