Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.