Skip to content

Commit d9ffe2f

Browse files
committed
Proposal to generate UUIDs using RandomNumberGenerators
This PR adds a proposal to generate `UUID's` using `RandomNumberGenerator`s
1 parent 80588e9 commit d9ffe2f

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

Proposals/NNNN-random-uuid.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Generating UUIDs using RandomNumberGenerators
2+
3+
* Proposal: [SF-NNNN](NNNN-random-uuid.md)
4+
* Authors: [FranzBusch](https://github.com/FranzBusch)
5+
* Review Manager: TBD
6+
* Status: **Awaiting review**
7+
* Implementation: [swiftlang/swift-foundation#1271](https://github.com/swiftlang/swift-foundation/pull/1271)
8+
* Review: ([pitch](https://forums.swift.org/...))
9+
10+
## Introduction
11+
12+
UUIDs (Universally Unique IDentifiers) are 128 bits long and is intended to
13+
guarantee uniqueness across space and time. This proposal adds APIs to generate
14+
UUIDs from Swift's random number generators.
15+
16+
## Motivation
17+
18+
UUIDs often need to be randomly generated. This is currently possible by calling
19+
the `UUID` initializer. However, this initializer doesn't allow providing a
20+
custom source from which the `UUID` is generated. Swift's standard library
21+
provides a common abstraction for random number generators through the
22+
`RandomNumberGenerator` protocol. Providing methods to generate `UUID`s using a
23+
`RandomNumberGenerator` allows developers to customize their source of randomness.
24+
25+
An example where this is useful is where a system needs to generate UUIDs using a
26+
deterministically seeded random number generator.
27+
28+
## Proposed solution
29+
30+
This proposal adds a new static method to the `UUID` type to generate new random `UUIDs` using a `RandomNumberGenerator`.
31+
32+
```swift
33+
/// Generates a new random UUID.
34+
///
35+
/// - Parameter generator: The random number generator to use when creating the new random value.
36+
/// - Returns: A random UUID.
37+
@available(FoundationPreview 6.2, *)
38+
public static func random(
39+
using generator: inout some RandomNumberGenerator
40+
) -> UUID
41+
```
42+
43+
## Source compatibility
44+
45+
The new API is purely additive and ha no impact on the existing API.
46+
47+
## Implications on adoption
48+
49+
This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility.
50+
51+
## Alternatives considered
52+
53+
### Initializer based random UUID generation
54+
55+
The existing `UUID.init()` is already generating new random `UUID`s and a new
56+
`UUID(using: &rng)` method would be a good alternative to the proposed static method.
57+
However, the static `random` method has precedence on various types such as [Int.random](https://developer.apple.com/documentation/swift/int/random(in:)-9mjpw).

Sources/FoundationEssentials/UUID.swift

+44
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,50 @@ public struct UUID : Hashable, Equatable, CustomStringConvertible, Sendable {
7676
hasher.combine(bytes: buffer)
7777
}
7878
}
79+
80+
/// Generates a new random UUID.
81+
///
82+
/// - Parameter generator: The random number generator to use when creating the new random value.
83+
/// - Returns: A random UUID.
84+
@available(FoundationPreview 6.2, *)
85+
public static func random(
86+
using generator: inout some RandomNumberGenerator
87+
) -> UUID {
88+
let first = UInt64.random(in: .min ... .max, using: &generator)
89+
let second = UInt64.random(in: .min ... .max, using: &generator)
90+
91+
var firstBits = first
92+
var secondBits = second
93+
94+
// Set the version to 4 (0100 in binary)
95+
firstBits &= 0xFFFFFFFFFFFF0FFF // Clear the last 12 bits
96+
firstBits |= 0x0000000000004000 // Set the version bits to '0100' at the correct position
97+
98+
// Set the variant to '10' (RFC9562 variant)
99+
secondBits &= 0x3FFFFFFFFFFFFFFF // Clear the 2 most significant bits
100+
secondBits |= 0x8000000000000000 // Set the two MSB to '10'
101+
102+
let uuidBytes = (
103+
UInt8(truncatingIfNeeded: firstBits >> 56),
104+
UInt8(truncatingIfNeeded: firstBits >> 48),
105+
UInt8(truncatingIfNeeded: firstBits >> 40),
106+
UInt8(truncatingIfNeeded: firstBits >> 32),
107+
UInt8(truncatingIfNeeded: firstBits >> 24),
108+
UInt8(truncatingIfNeeded: firstBits >> 16),
109+
UInt8(truncatingIfNeeded: firstBits >> 8),
110+
UInt8(truncatingIfNeeded: firstBits),
111+
UInt8(truncatingIfNeeded: secondBits >> 56),
112+
UInt8(truncatingIfNeeded: secondBits >> 48),
113+
UInt8(truncatingIfNeeded: secondBits >> 40),
114+
UInt8(truncatingIfNeeded: secondBits >> 32),
115+
UInt8(truncatingIfNeeded: secondBits >> 24),
116+
UInt8(truncatingIfNeeded: secondBits >> 16),
117+
UInt8(truncatingIfNeeded: secondBits >> 8),
118+
UInt8(truncatingIfNeeded: secondBits)
119+
)
120+
121+
return UUID(uuid: uuidBytes)
122+
}
79123

80124
public var description: String {
81125
return uuidString

Tests/FoundationEssentialsTests/UUIDTests.swift

+19
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,23 @@ final class UUIDTests : XCTestCase {
115115
XCTAssertFalse(uuid2 > uuid1)
116116
XCTAssertTrue(uuid2 == uuid1)
117117
}
118+
119+
func testRandomVersionAndVariant() {
120+
var generator = SystemRandomNumberGenerator()
121+
for _ in 0..<10000 {
122+
let uuid = UUID.random(using: &generator)
123+
XCTAssertEqual(uuid.versionNumber, 0b0100)
124+
XCTAssertEqual(uuid.varint, 0b10)
125+
}
126+
}
127+
}
128+
129+
extension UUID {
130+
fileprivate var versionNumber: Int {
131+
Int(self.uuid.6 >> 4)
132+
}
133+
134+
fileprivate var varint: Int {
135+
Int(self.uuid.8 >> 6 & 0b11)
136+
}
118137
}

0 commit comments

Comments
 (0)