Last week I wrote about the BooleanType protocol. Now that we have open-source Swift, it’s time to look at the mother of all BooleanType
types: Bool
itself!
Close reading
Rather than the usual computer science or physics or philosophy background of most developers, I was an English major. We spent a good amount of time doing close readings of prose passages and poetry.
I thought, why not put all that education to good use and try something similar for code?
After all — Prose : Documentation :: Poetry : Code, right?
Bool.swift
The code is all inline below, but you can see the original file in the Swift repository too: Bool.swift.
I picked a seemingly simple example to start as a good way into the Swift standard library source code for me. Let’s get started!
/// A value type whose instances are either `true` or `false`.
public struct Bool {
No surprise, it’s a struct
. I still hear from many people who are amazed at the heavy use of value types, but Swift is massively tilted in that direction: structs outnumber classes by a factor of 20 in the standard library.
Storage
internal var _value: Builtin.Int1
Here’s the actual storage: a one-bit integer. We’ll presumably get 1
for true
and 0
for false
.
Builtin
means the thing is coming from “one layer down”. If you’ve looked into SIL, you’ll see this all over the place when it digs down to the LLVM layer.
In this case, Int1
should map to the LLVM type of the same name further down the line.
In terms of style, they’re using a leading underscore for non-public properties. Also, note that the property is internal
rather than private
; that means other modules in the standard library can access this bit directly when needed.
Initializers
/// Default-initialize Boolean value to `false`.
@_transparent
public init() {
let zero: Int8 = 0
self._value = Builtin.trunc_Int8_Int1(zero._value)
}
Here’s the public initializer. By default, Bool
instances get a default value of false
.
There are some minor acrobatics here to get a zero value into the _value
property. While Swift has a native Int8
to go with the Builtin.Int8
, there’s no native Int1
.
To get that one-bit zero, the strategy here is to start with an Int8
. From there, there’s another handy Builtin
to truncate the 8-bit value to a 1-bit value.
Of course trunc_Int8_Int1
takes a Builtin.Int8
rather than a Swift Int8
. As with Bool
, Int8
has a _value
property to access the raw built-in value. And since it’s internal
rather than private
, we have access to it here!
What’s @_transparent
? Here’s a note from the documentation:
Semantically,
@_transparent
means something like “treat this operation as if it were a primitive operation”. The name is meant to imply that both the compiler and the compiled program will “see through” the operation to its implementation.
Check out the docs for the gory details, but think of @_transparent
functions as unchanging inlined functions that don’t rely on private or internal details.
@_transparent
internal init(_ v: Builtin.Int1) { self._value = v }
Here’s the second initializer, with a more common structure: it takes a Builtin.Int1
and assigns it directly to the _value
property. This initializer is internal
-only, which makes sense since only the standard library code will have easy access to Builtin.Int1
values anyway.
BooleanLiteralConvertible
extension Bool : _BuiltinBooleanLiteralConvertible, BooleanLiteralConvertible {
@_transparent
public init(_builtinBooleanLiteral value: Builtin.Int1) {
self._value = value
}
/// Create an instance initialized to `value`.
@_transparent
public init(booleanLiteral value: Bool) {
self = value
}
}
Bool
conforms to BooleanLiteralConvertible
and its more private counterpart _BuiltinBooleanLiteralConvertible
.
Nothing too surprising here: the protocols have required initializers and they’re implemented here: one that takes a literal Builtin.Int1
and the other that takes a Bool
.
There’s some duplication here since we’ve already seen an initializer that takes a Builtin.Int1
value. They have different external parameter names and one is internal
vs public
, but the implementations are identical.
BooleanType
extension Bool : BooleanType {
@_transparent
@warn_unused_result
public func _getBuiltinLogicValue() -> Builtin.Int1 {
return _value
}
/// Identical to `self`.
@_transparent public var boolValue: Bool { return self }
/// Construct an instance representing the same logical value as `value`.
public init<T : BooleanType>(_ value: T) {
self = value.boolValue
}
}
Bool
should, of course, conform to BooleanType
, which has one required property: boolValue
. That needs to return a Bool
which makes the implementation here very simple: return self
.
There are two additional things here in the extension:
_getBuiltinLogicValue()
— returns that internalBuiltin.Int1
value of theBool
, which seems to break encapsulation. I can only find two instances where the method is called in the standard library code, both in the set of SIL tests.Another initializer that takes anything that conforms to
BooleanType
. That makes sense: you should be able to initialize aBool
from any suchBooleanType
.
@warn_unused_result
makes its first appearance. That’s what triggers the “Result of call is unused” warning you see in Xcode.
CustomStringConvertible
extension Bool : CustomStringConvertible {
/// A textual representation of `self`.
public var description: String {
return self ? "true" : "false"
}
}
Playground users and people debugging their code all around the world will love you forever if you remember to implement CustomStringConvertible
for your types.
Magic conversion
// This is a magic entrypoint known to the compiler.
@_transparent
public // COMPILER_INTRINSIC
func _getBool(v: Builtin.Int1) -> Bool { return Bool(v) }
It’s magic!
Intrinsic functions are indeed magic since the compiler will have special knowledge of them.
In this case, the function is a shortcut to turn one of those Builtin.Int1
values into a Bool
using the initializer we’ve already seen.
This function is @_transparent
and will be inlined away—as will the initializer, come to think of it. It’s inlining all the way down!
Equatable & Hashable
@_transparent
extension Bool : Equatable, Hashable {
public var hashValue: Int {
return self ? 1 : 0
}
}
Best practice for value types is to make them Equatable
and Hashable
.
For Equatable
, you need to provide an implementation for ==
, which you’ll see in a bit.
For Hashable
, you need to hash the value down to an Int
. That’s easy in this case: true
is 1 and false
is 0.
Unary logical complement
@_transparent
@warn_unused_result
public prefix func !(a: Bool) -> Bool {
return Bool(Builtin.xor_Int1(a._value, true._value))
}
We’re coming to the end, which must mean: more public functions!
Here’s the !
operator to turn true
into false
and vice versa. The obvious solution to toggle the bit might be something like this:
return a ? false : true
But think back to bitwise operations instead. How do you toggle a bit? XOR it with 1, of course! That’s what’s happening here: xor_Int1
with the argument’s value and the Int1
representation of true
.
I guess when you go way down to assembly, this will become a plain old XOR instruction, which is super fast. Contrast that to the obvious implementation, which has a branch.
Trivia note from the early x86 days: XOR'ing a register with itself (
XOR AX, AX
) was the fastest way to zero out the value since the instruction was shorter thanMOV AX, 0
.
Equatable
@_transparent
@warn_unused_result
public func ==(lhs: Bool, rhs: Bool) -> Bool {
return Bool(Builtin.cmp_eq_Int1(lhs._value, rhs._value))
}
Here’s the ==
operator to make Equatable
happy.
Again, the implementation uses a built-in to compare the Int1
values directly. Perhaps this will compile down to a CMP
in assembly to again be super fast?
Final thoughts
That was a lot of words for a relatively simple type!
Lifting up the covers of a fundamental type such as Bool
offers plenty of coding tips and implementation details. Some highlights:
Two-space tabs — That’s spaces, not tabs. I feel like I would fit right into the Swift team in case you’re reading, Apple. ;)
Protocols — Type functionality is split up by protocol to keep things organized.
Extensions — Each protocol conformance is in its own extension. I’ve used that style since the Objective-C days and am glad to see it here too.
Built-ins — In contrast to C, Swift is more levels above the metal: in the LLVM workflow, you’re going from Swift to SIL to LLVM IR to machine code. Built-ins are a neat way to move down one level and let a lower-level implementation know your intentions so it can optimize accordingly.
There’s what I like best about open-source: getting below the surface and seeing how things work.
I hope this was useful and a fun read! Share, let me know what you think, and in the meantime I plan to keep poking around the source.