Skip to content

Commit df12e88

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 df12e88

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

Proposals/0024-random-uuid.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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(macOS 10.8, iOS 6.0, tvOS 9.0, watchOS 2.0, *)
38+
@backDeployed(before: FoundationPreview 6.2)
39+
public static func random(
40+
using generator: inout some RandomNumberGenerator
41+
) -> UUID
42+
```
43+
44+
## Source compatibility
45+
46+
The new API is purely additive and ha no impact on the existing API.
47+
48+
## Implications on adoption
49+
50+
"This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility."

Proposals/NNNN-random-uuid.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Random UUID generation
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(macOS 10.8, iOS 6.0, tvOS 9.0, watchOS 2.0, *)
38+
@backDeployed(before: FoundationPreview 6.2)
39+
public static func random(
40+
using generator: inout some RandomNumberGenerator
41+
) -> UUID
42+
```
43+
44+
## Source compatibility
45+
46+
The new API is purely additive and ha no impact on the existing API.
47+
48+
## Implications on adoption
49+
50+
"This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility."

Sources/FoundationEssentials/UUID.swift

+45
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,51 @@ 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(macOS 10.8, iOS 6.0, tvOS 9.0, watchOS 2.0, *)
85+
@backDeployed(before: FoundationPreview 6.2)
86+
public static func random(
87+
using generator: inout some RandomNumberGenerator
88+
) -> UUID {
89+
let first = UInt64.random(in: .min ... .max, using: &generator)
90+
let second = UInt64.random(in: .min ... .max, using: &generator)
91+
92+
var firstBits = first
93+
var secondBits = second
94+
95+
// Set the version to 4 (0100 in binary)
96+
firstBits &= 0xFFFFFFFFFFFF0FFF // Clear the last 12 bits
97+
firstBits |= 0x0000000000004000 // Set the version bits to '0100' at the correct position
98+
99+
// Set the variant to '10' (RFC9562 variant)
100+
secondBits &= 0x3FFFFFFFFFFFFFFF // Clear the 2 most significant bits
101+
secondBits |= 0x8000000000000000 // Set the two MSB to '10'
102+
103+
let uuidBytes = (
104+
UInt8(truncatingIfNeeded: firstBits >> 56),
105+
UInt8(truncatingIfNeeded: firstBits >> 48),
106+
UInt8(truncatingIfNeeded: firstBits >> 40),
107+
UInt8(truncatingIfNeeded: firstBits >> 32),
108+
UInt8(truncatingIfNeeded: firstBits >> 24),
109+
UInt8(truncatingIfNeeded: firstBits >> 16),
110+
UInt8(truncatingIfNeeded: firstBits >> 8),
111+
UInt8(truncatingIfNeeded: firstBits),
112+
UInt8(truncatingIfNeeded: secondBits >> 56),
113+
UInt8(truncatingIfNeeded: secondBits >> 48),
114+
UInt8(truncatingIfNeeded: secondBits >> 40),
115+
UInt8(truncatingIfNeeded: secondBits >> 32),
116+
UInt8(truncatingIfNeeded: secondBits >> 24),
117+
UInt8(truncatingIfNeeded: secondBits >> 16),
118+
UInt8(truncatingIfNeeded: secondBits >> 8),
119+
UInt8(truncatingIfNeeded: secondBits)
120+
)
121+
122+
return UUID(uuid: uuidBytes)
123+
}
79124

80125
public var description: String {
81126
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)