Generating Random Numbers Elegantly in Swift
This post is brought to you by Emerge Tools, the best way to build on mobile.
I love caffeine. Like, I love caffeine. Lately, I’ve been pecking away at a little pet project to log mine. Since I mostly consume espresso, my caffeine logging usually boils down to one of four choices. I log either a…
- Single shot (~ 64mgs of caffeine)
- Double shot (~ 128mgs of caffeine)
- Triple shot (~ 192mgs of caffeine)
- Quad shot (~ 256mgs of caffeine)
As such, I have this little guy:
enum EspressoShot: Int, CaseIterable {
case single = 64, double = 128, triple = 192, quadShot = 256
}
While whipping up some testing data, I wanted a random value from those. Easy enough to support:
enum EspressoShot: Int, CaseIterable {
case single = 64, double = 128, triple = 192, quadShot = 256
static func randomShot() -> EspressoShot {
return EspressoShot.allCases.randomElement() ?? .single
}
}
Certainly, that works:
let randomShot: EspressoShot = EspressoShot.randomShot()
But, after browsing some docs, I saw that you can easily plug in Swift’s SystemRandomNumberGenerator
for your own types. RandomNumberGenerator
itself is a protocol, but you don’t have to manually adopt it most of the time. Take a look:
enum EspressoShot: Int, CaseIterable {
case single = 64, double = 128, triple = 192, quadShot = 256
static func random<G: RandomNumberGenerator>(using generator: inout G) -> EspressoShot {
return EspressoShot.allCases.randomElement(using: &generator)!
}
static func random() -> EspressoShot {
var g = SystemRandomNumberGenerator()
return EspressoShot.random(using: &g)
}
}
The first function supports passing in any random number generator that adopts the bespoke protocol. However, the bottom one is super useful - we can simply use the built-in random number generator, and then pass it to the previous function to get our random value:
let randomShot: EspressoShot = EspressoShot.random()
Beautiful. Here’s why I like this:
- The call site is a bit leaner.
- It’s also more familiar, as
Type.random()
is a common Swift convention. - We don’t have to deal with force unwraps at the call site. It’s hidden.
- It also supports using any random number generator, while our hand rolled one could not.
- I’ll always use Swift’s implementation whenever possible. Their random number generator is automatically seeded, thread-safe and uses the appropriate APIs depending on the platform Swift is running (i.e. Windows (
BCryptGenRandom
), Linux (getrandom(2)
) or Apple (arc4random_buf(3)
)).
It’s de rigueur for Swift codebases to be, well….Swifty, is it not? And this feels a bit more Swifty to me.
Update: The open source maestro himself, Sindre Sorhus, offered a nice solution wherein you can make any type conforming to CaseIterable
have these capabilities:
extension CaseIterable {
public static func randomCaseIterableElement(using generator: inout some RandomNumberGenerator) -> Self {
allCases.randomElement(using: &generator)!
}
public static func randomCaseIterableElement() -> Self {
var generator = SystemRandomNumberGenerator()
return randomCaseIterableElement(using: &generator)
}
}
enum Foo: String, CaseIterable {
case a
case b
case c
}
print(Foo.randomCaseIterableElement())
Check out his gist here.
Until next time ✌️.