2. Types, Operators, and Expressions
Every value in Swift has a type, the type is known before the program runs, and nothing is converted behind your back. Those three facts account for most of what this chapter contains and for most of the errors you will see in your first week.
2.1 Names
A name is a letter or underscore followed by letters, digits and underscores.
Swift’s idea of a letter is Unicode’s, so Δx and café are names, and so are
most emoji, which is a joke the language allows and you should not accept.
Swift’s convention is lowerCamelCase for variables, functions and properties,
UpperCamelCase for types, and no underscores in either. Capitalization is how
a reader tells a type from a value, so it is worth obeying.
Keywords like let, func and class are reserved, but only softly. Wrap a
keyword in backticks and it becomes a name:
let `class` = "Physics 101"
That exists so Swift can talk to systems whose field names it does not control, and it is not for you.
Prefer let. The compiler warns when a local var is never assigned a second
time, and says which word to change, so the habit gets enforced from the first
day.
2.2 Integers
Int is the integer type. It is signed, it is the width of a pointer on the
machine, and that means 64 bits everywhere you are likely to run. Use it. The
sized types Int8, Int16, Int32, Int64 and their unsigned partners
UInt8 through UInt64 exist for when the width is part of the problem, as in
a file format or a hardware register, and UInt exists and is almost always the
wrong choice. A quantity that cannot be negative is still an Int, because the
moment you subtract two of them you want the answer to be allowed to go below
zero.
// What the numeric types hold.
print("Int ", Int.min, Int.max)
print("Int8 ", Int8.min, Int8.max)
print("UInt8 ", UInt8.min, UInt8.max)
print("Int32 ", Int32.min, Int32.max)
print("Double ", Double.leastNormalMagnitude, Double.greatestFiniteMagnitude)
print("Int size ", MemoryLayout<Int>.size, "bytes")
Int -9223372036854775808 9223372036854775807
Int8 -128 127
UInt8 0 255
Int32 -2147483648 2147483647
Double 2.2250738585072014e-308 1.7976931348623157e+308
Int size 8 bytes
Every numeric type carries its own limits as min and max, which is better
than the header file full of macros that C needs for the same information.
Literals may be written in decimal, hex with 0x, octal with 0o, or binary
with 0b, and underscores may be dropped in anywhere to group digits:
1_000_000, 0b1010_1111. The underscores are ignored.
Integer arithmetic that leaves the range of its type stops the program. It does not wrap around and it does not quietly produce garbage, which is the single largest behavioral difference between Swift arithmetic and C arithmetic. Where you want wrapping you ask for it by name.
// Swift traps on overflow. The & operators wrap instead, on purpose.
var n = Int8.max
print(n)
print(n &+ 1)
print(Int8.max.addingReportingOverflow(1))
let (sum, carried) = Int.max.addingReportingOverflow(1)
if carried {
print("overflowed; the wrapped answer was \(sum)")
}
127
-128
(partialValue: -128, overflow: true)
overflowed; the wrapped answer was -9223372036854775808
&+, &- and &* wrap the way C does. They are for hash functions, checksums,
and code that is deliberately doing modular arithmetic. The ...ReportingOverflow
methods hand back both the wrapped answer and whether it wrapped, for when you
want to notice without crashing.
2.3 Floating Point
Double is 64-bit and is what an unannotated literal with a decimal point
becomes. Float is 32-bit and you use it when something else demands it.
Float16 exists on platforms that support it. There is no decimal type in the
standard library, so money belongs in an Int count of cents or in Foundation’s
Decimal, never in a Double.
Floating literals may carry an exponent, 1.6e-19, and may be written in hex,
0x1p-3 for one-eighth. Division by zero gives infinity rather than a crash,
and 0.0 / 0.0 gives nan, which compares unequal to everything including
itself. x.isNaN is how you ask.
2.4 Bool, Character, and String
Bool is true or false and nothing else is a Bool. Swift has no truthiness:
an empty array is not false, zero is not false, and nil is not false.
int_as_bool.swift:2:4: error: type 'Int' cannot be used as a boolean; test for '!= 0' instead
1 | let count = 3
2 | if count {
| `- error: type 'Int' cannot be used as a boolean; test for '!= 0' instead
Coming from Python or JavaScript this is the rule you will trip over most often
in the first hour and miss least afterwards, because if items.isEmpty says
what it means and if items never quite did.
A Character is a single character as a reader understands it, written in
single or double quotes — Swift uses double quotes for both, and the type
decides which you got. A String is a sequence of them. Chapter 6 is about what
that costs; for now, + joins strings, += appends, == compares by content,
and \(...) interpolates.
2.5 Conversions
Swift converts nothing implicitly. A conversion is written by naming the type you want and calling it, which looks like a cast and is an ordinary initializer:
// Conversions are initializers, and some of them can fail.
import Foundation
let pi = 3.9
print(Int(pi)) // 3, truncated toward zero
print(Int(pi.rounded())) // 4, rounded first
let big = 300
print(UInt8(truncatingIfNeeded: big)) // 44, the low eight bits
// The exact forms hand back nothing when the value will not fit.
if let small = UInt8(exactly: 200) {
print("200 fits in a UInt8 as \(small)")
}
if UInt8(exactly: big) == nil {
print("300 does not fit in a UInt8")
}
3
4
44
200 fits in a UInt8 as 200
300 does not fit in a UInt8
Int(someDouble) truncates toward zero and stops the program if the value is
too large to represent or is a nan. Int(exactly:) hands back nothing instead
of truncating, and UInt8(truncatingIfNeeded:) keeps the low bits and asks no
questions. Three behaviors, three spellings, and the one you get is the one you
named. C has the same three behaviors and one spelling for all of them.
The rule that values do not convert has one deliberate hole, and it is the hole
that makes arithmetic bearable. A literal has no type until the expression needs
one, so let x: Double = 3 and let y = 2 * someDouble both work. What fails
is mixing two values that already have types, which is the error from Chapter 1.
Values written down are flexible; values stored in a name are not.
2.6 Operators
The arithmetic operators are + - * / %, and % is a remainder rather than a
modulus: it takes the sign of the left operand, which is C’s rule and not
Python’s.
// Integer division truncates toward zero, and the remainder follows it.
print(7 / 2, 7 % 2)
print(-7 / 2, -7 % 2)
print(7 / -2, 7 % -2)
print(7.0 / 2.0, 7.0.truncatingRemainder(dividingBy: 2.0))
3 1
-3 -1
-3 1
3.5 1.0
% does not accept floating-point operands, because the answer people expect is
not the answer the hardware gives; truncatingRemainder(dividingBy:) is the
spelling that makes you admit what you asked for.
Comparison is == != < <= > >= and works on anything that declares it can be
compared, which Chapter 10 explains. && and || short-circuit, ! negates,
and all three take Bool and nothing else.
The conditional operator is C’s: let n = a > b ? a : b. Swift also has ??,
which supplies a value when an optional has none, and that is Chapter 5.
Ranges are values in their own right, not loop syntax. 0..<n is half-open,
0...n is closed, a... and ...b are one-sided, and all of them can be
stored in a variable, passed to a function, or asked whether they contain
something with range.contains(x).
Assignment does not produce a value. if x = y is not a subtle bug in Swift, it
is a compile error, and the class of mistake C spends a warning flag on does not
exist here.
Exercise 2-1. Write a function that returns the number of 1 bits in an
Int, and check it against the standard library’s nonzeroBitCount.
Exercise 2-2. 7 % -2 is 1 and Python says -1. Write a function
mod(_:_:) that always returns a non-negative result for a positive divisor.
Exercise 2-3. Find out what Int(Double.nan) does, and what
Int(exactly: Double.nan) does instead.
2.7 Tuples
A tuple gathers a few values into one without going to the trouble of declaring a type. It is the right answer for a function that has two things to say and the wrong answer for anything that outlives the call.
// A tuple gathers a few values without naming a type for them.
func extremes(_ a: [Int]) -> (low: Int, high: Int) {
var low = a[0]
var high = a[0]
for n in a {
if n < low { low = n }
if n > high { high = n }
}
return (low, high)
}
let range = extremes([3, 9, 1, 14, 7])
print(range.low, range.high)
print(range.0, range.1)
let (lo, hi) = extremes([3, 9, 1, 14, 7])
print("from \(lo) to \(hi)")
let point = (x: 1.5, y: -2.0)
print(point.x + point.y)
1 14
1 14
from 1 to 14
-0.5
Elements are reached by name when the tuple has names and by position when it
does not. A tuple can be taken apart in a let, and _ discards a piece you do
not want: let (_, hi) = extremes(a).
Tuples compare with == and sort lexicographically, which makes
(b.rank, b.name) < (a.rank, a.name) a readable two-key comparison. They cannot
have methods, cannot be extended, and cannot be stored in most places that want
a real type. When a tuple starts being passed between functions, promote it to a
struct and give the fields real names; Chapter 7 shows what that costs, which
is four lines.
2.8 When to Write the Type
Inference is not a convenience feature bolted on the side; it is how Swift is
written. let count = 0 and let names = ["ada", "grace"] need no annotation
and get none.
Write the type when the literal is ambiguous and you care which way it resolves,
let ratio: Double = 1; when you are declaring something empty, var seen: [String] = [], because there is nothing to infer from; when the value comes
from a function whose return type is not obvious at the call; and on every
stored property of a type and every parameter and return, where Swift requires
it anyway.
Do not write it to document the obvious. let name: String = "ada" is noise,
and a reader who needs to be told that a quoted string is a String has larger
problems.
A typealias gives an existing type a second name, which is worth doing when a
type is long or when a name carries meaning the type does not:
typealias Celsius = Double
typealias Handler = (String) -> Void
It creates no new type and no checking. Celsius and Double are
interchangeable everywhere, so this is documentation rather than safety. Chapter
7 shows how to get the safety, when you want it, by wrapping the value in a
struct instead.
Exercise 2-4. Write a function that takes a number of seconds and returns a tuple of hours, minutes and seconds.
Exercise 2-5. Sort an array of (name: String, score: Int) by score
descending and name ascending, using tuple comparison.