1. A Tutorial Introduction
This chapter is a tour. It moves fast, shows real programs, and leaves the careful definitions for later chapters. The goal is to get enough Swift into your hands that the rest of the book has something to build on. Things you do not fully understand here are explained properly further along, and the text says where.
1.1 Getting Started
The only way to start.
print("hello, world")
Put it in a file called hello.swift, compile it with swiftc hello.swift,
and run the hello that appears beside it.
There is no main function and no enclosing class. Statements written at the
top level of a file run in order, which makes a Swift file about as ceremonious
as a Python one. A larger program does eventually want a proper entry point, and
Chapter 11 shows how, but nothing before it needs one.
print is a function, not a keyword and not a macro. It takes any number of
values, prints them separated by spaces, and adds a newline. The separator and
the ending are arguments you can change, which is how you print without the
newline:
print("no", "newline", separator: "-", terminator: "")
Exercise 1-1. Run the program. Then delete the closing parenthesis and compile again. Read the error.
Exercise 1-2. Print several lines with one print call. Find out what \n
and \t do inside the quotes, and what \\ and \" are for.
1.2 Variables and Arithmetic Expressions
The next program prints a table of Fahrenheit temperatures and their Celsius equivalents, using the formula C = (5/9)(F−32).
// Print a Fahrenheit-Celsius table for fahr = 0, 20, ..., 300.
let lower = 0
let upper = 300
let step = 20
var fahr = lower
while fahr <= upper {
let celsius = 5 * (fahr - 32) / 9
print("\(fahr)\t\(celsius)")
fahr += step
}
It prints:
0 -17
20 -6
40 4
...
300 148
Comments run from // to the end of the line. /* ... */ also works and nests
properly, but the line form is what Swift programmers use.
let introduces a name whose value never changes; var introduces one that
can. Immutable is the default and mutable is the word you type, which is
backwards from most scripting languages and is the first small example of
Swift’s general habit: the restricted thing is free and the flexible thing costs
a word. fahr is a var because the loop assigns to it. The other three never
change, so they are not. If you get it wrong the compiler is specific about it.
assign_let.swift:2:1: error: cannot assign to value: 'fahr' is a 'let' constant
1 | let fahr = 0
| `- note: change 'let' to 'var' to make it mutable
2 | fahr = fahr + 20
| `- error: cannot assign to value: 'fahr' is a 'let' constant
The note is usually the literal fix, and it repays reading rather than
skimming. Appendix C takes the diagnostics apart properly, and is
better read once you have collected a few of your own.
Notice there are no type declarations. Swift works out that these are integers
from the values they are given and settles on Int, a signed integer the width
of a machine word, which is 64 bits everywhere you are likely to run this. You
can write the type when you want to, and sometimes you must:
let upper: Double = 300
The while loop is what you expect, except that the condition needs no
parentheses and the braces are not optional, not even around a single statement.
fahr += step adds and assigns. There is no ++.
Inside a string, \(fahr) substitutes the value of the expression between the
parentheses. That is string interpolation, it takes any expression and not
just a name, and it is how nearly all Swift output gets built:
print("\(fahr) \(5 * (fahr - 32) / 9)")
Now the arithmetic, where this program lies to you. 5 * (fahr - 32) / 9 is
integer arithmetic throughout, so the division truncates toward zero and the
table is wrong in the last digit. C has the same trap and K&R spends the same
paragraph on it. The fix is floating point.
// Same table, floating point, right-aligned columns.
import Foundation
let lower = 0.0
let upper = 300.0
let step = 20.0
var fahr = lower
while fahr <= upper {
let celsius = (fahr - 32.0) * 5.0 / 9.0
print(String(format: "%3.0f %6.1f", fahr, celsius))
fahr += step
}
0 -17.8
20 -6.7
40 4.4
60 15.6
80 26.7
100 37.8
A literal with a decimal point is a Double, a 64-bit float. The standard
library has no formatted printing, so column alignment comes from Foundation’s
String(format:), which is C’s printf with the same conversions: %3.0f
means a minimum width of three characters and no digits after the point,
%6.1f means width six and one digit. Width pads, it does not truncate.
import Foundation is the line that makes it available, and you will type it
often, because Swift’s standard library is deliberately small and Foundation
holds dates, files, regular expressions and the rest of what a program needs.
Swift does not convert between number types on its own. Adding an Int to a
Double is not an error you have to think hard to make:
mixed_numbers.swift:3:17: error: binary operator '+' cannot be applied to operands of type 'Double' and 'Int'
3 | let next = fahr + step
| |- error: binary operator '+' cannot be applied to operands of type 'Double' and 'Int'
| `- note: overloads for '+' exist with these partially matching parameter lists: (Double, Double), (Int, Int)
Conversions are written by naming the type you want, Double(step) or
Int(fahr). Literals are the exception and are looser than that suggests, so
5 * (fahr - 32.0) / 9 compiles even with fahr a Double. Chapter 2 says why,
and what each conversion costs.
Exercise 1-3. Add a heading to the table.
Exercise 1-4. Print the table in reverse order, from 300 degrees to 0.
Exercise 1-5. Write a program that converts Celsius to Fahrenheit.
1.3 The for Statement
The same table again, written the way a Swift programmer would write it:
// Same table again, as a for loop over a range.
import Foundation
for i in 0...15 {
let fahr = Double(i) * 20.0
print(String(format: "%3.0f %6.1f", fahr, (fahr - 32.0) * 5.0 / 9.0))
}
0...15 is a range that includes both ends. 0..<16 is the same range with the
top end excluded, and the half-open form is the one you will use most, because
0..<a.count is the natural way to talk about the positions in a collection of
a.count things. Getting the two mixed up is the classic off-by-one, and Swift
at least makes you say which you meant.
Swift’s for is not C’s. It has no three clauses; it walks something. A range is
one such something, and so is an array, a dictionary, a string, or the lines of
a file. This is the same for you know from Python, with the same comfortable
property that the loop cannot run off the end of the thing it is walking.
Double(i) converts the integer to a float. It looks like a cast and is in
fact a call to an initializer on the type, which is why a conversion behaves
like the ordinary function call it is.
The loop variable is a let even though you did not write one, so assigning to
i inside the loop is an error. Say for var i in ... if you need to, and you
almost never do.
Exercise 1-6. Rewrite the temperature table as a loop over Fahrenheit values
rather than a count, using stride(from: 0, through: 300, by: 20).
1.4 Input and Output
Here is a program that copies its input to its output.
// Copy input to output, a line at a time.
while let line = readLine(strippingNewline: false) {
print(line, terminator: "")
}
Run it with ./copy < in.txt and it reproduces the file.
readLine() reads one line from standard input and hands back the text of it.
When the input runs out there is no line to hand back, and this is the moment
Swift differs from every scripting language you have used. It does not return an
empty string, and it does not return null, because Swift has no null. It returns
a String?, an optional string, which either contains a String or contains
nothing, and which the compiler treats as a different type:
unwrapped_line.swift:2:7: error: value of optional type 'String?' must be unwrapped to refer to member 'count' of wrapped base type 'String'
1 | let line = readLine()
2 | print(line.count)
| |- note: chain the optional using '?' to access member 'count' only for non-'nil' base values
| `- note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
You cannot use the string without saying what should happen if it is not there.
while let line = readLine(...) is the short way of saying it: take the value
out, bind it to line, run the body, and stop the loop the first time there is
nothing to take. The loop condition and the unwrapping are one move. Chapter 5
is about optionals and they are the feature you will meet most often, so it
comes early.
strippingNewline: false keeps the line ending, which a copy program wants.
Without it you get the line without its newline, which is what everything else
wants. That name before the colon is an argument label, and Section 1.6
explains why Swift calls arguments by name.
Counting
This is wc, more or less: lines, words, and bytes.
// Count lines, words, and bytes on standard input.
var text = ""
while let line = readLine(strippingNewline: false) {
text += line
}
var lines = 0
var words = 0
var inWord = false
for c in text {
if c == "\n" {
lines += 1
}
if c == " " || c == "\n" || c == "\t" {
inWord = false
} else if !inWord {
inWord = true
words += 1
}
}
print(lines, words, text.utf8.count)
On a two-line file it agrees with the system’s wc:
$ ./count < in.txt
2 6 28
$ wc < in.txt
2 6 28
The word-counting logic is the state machine from K&R: you are in a word or you are not, and a transition from “not” to “in” is a new word.
Walking a string with for c in text gives you Character values, which are
neither bytes nor Unicode code points. That is why the byte count here is
text.utf8.count and not text.count: the two differ the moment anything
outside English shows up, and Swift makes you name the one you meant. Chapter 6
is about the difference.
print(lines, words, text.utf8.count) passes three values and gets them
separated by spaces, which is print’s default and is why the output lines up
with wc without any formatting.
Arrays
Counting occurrences of each digit, plus white space and everything else:
// Count digits, white space, and everything else.
var text = ""
while let line = readLine(strippingNewline: false) {
text += line
}
var digits = [Int](repeating: 0, count: 10)
var white = 0
var other = 0
for c in text {
switch c {
case "0"..."9":
digits[c.wholeNumberValue!] += 1
case " ", "\t", "\n":
white += 1
default:
other += 1
}
}
var line = "digits ="
for n in digits {
line += " \(n)"
}
print("\(line), white space = \(white), other = \(other)")
$ printf 'abc 123 4\t56\n' | ./histogram
digits = 0 1 1 1 1 1 1 0 0 0, white space = 4, other = 3
[Int](repeating: 0, count: 10) makes an array of ten integers, all zero. A
Swift array grows and shrinks, its length is not part of its type, and you get
at the elements with digits[i] counting from zero. An index past the end stops
the program with a message rather than reading whatever is next in memory.
switch is the other new thing, and it is the control structure Swift leans on
hardest. Here "0"..."9" is a range pattern, " ", "\t", "\n" is a list of
alternatives, and default catches the rest, which a Character needs because a
switch has to cover every case. Chapter 3 is where it gets its due, and Chapter
8 shows why the language is built around it.
c.wholeNumberValue is the digit’s numeric value, and it is an optional,
because most characters do not have one. The ! after it is a force unwrap:
the short, rude way of saying “this cannot be nothing, and if it is, crash”. It
is safe here, because the case above it has already established that c is a
digit, and it is a smell almost everywhere else. Chapter 5 gives you the polite
forms.
Exercise 1-7. Write a program that prints its input one word per line.
Exercise 1-8. Count blanks, tabs, and newlines separately.
Exercise 1-9. Copy input to output, replacing each run of one or more blanks with a single blank.
1.5 Functions
A function to raise an integer to a power:
// Raise base to the n-th power; n >= 0.
func power(_ base: Int, _ n: Int) -> Int {
var p = 1
for _ in 0..<n {
p *= base
}
return p
}
for i in 0..<10 {
print(i, power(2, i), power(-3, i))
}
0 1 1
1 2 -3
2 4 9
3 8 -27
...
9 512 -19683
func introduces a function. Parameters must have their types written out, and
so must the return type, after the arrow. A function that returns nothing omits
the arrow. Inside the body, inference takes over again: p is an Int because
it starts at 1 and is multiplied by one.
return is required, with one exception: a function whose body is a single
expression may leave it out, so func double(_ n: Int) -> Int { n * 2 } is
complete. Swift programmers use that for one-liners and write return
everywhere else.
The loop variable is _, which is how you say you do not intend to use it. The
underscore means “discard this” throughout Swift, and you will see it in several
places before the book is out.
Exercise 1-10. Write power using recursion.
Exercise 1-11. power(2, 63) stops the program rather than wrapping around
to a negative number the way C would. Find out what &* does instead, and when
you would want it.
1.6 Arguments
Swift calls arguments by name, and that is what the underscore in power’s
definition was turning off. Python has keyword arguments too, but they are
optional and Swift’s are not: the labels are part of the call and leaving one
out is an error.
A parameter has two names, the label the caller writes and the name the body uses. Write one name and it serves as both.
func move(from: Int, to: Int) -> Int { to - from }
let d = move(from: 3, to: 10)
Write two and the first is the caller’s, the second the body’s, which is how you get a call that reads as a sentence and a body that reads as code:
func move(from start: Int, to end: Int) -> Int { end - start }
Write _ as the label and the caller writes nothing, which is what power did
and what you want when the argument’s role is obvious from the function’s name.
The labels are part of the function’s identity: move(from:to:) is its full
name, and two functions may share the word move and differ only in their
labels. Getting this right is most of what makes Swift code read the way it
does, and Chapter 4 covers the rest of the rules.
Arguments arrive as constants. A parameter is a let inside the body, so
power cannot count n down to zero the way the C version does and loops over
a range instead. To hand a function something it can modify in place you
mark the parameter inout and the caller writes & at the call, and Chapter 4
shows that.
What gets copied is the larger question. Passing an Int copies it and nothing
is surprising. Passing an array or a string or a value of a type you defined
with struct also copies it, so the function cannot change the caller’s copy
however hard it tries. Passing an object of a type you defined with class does
not copy: both sides hold the same object, and a change made through one is
visible through the other. That difference is the subject of Chapter 7 and there
is nothing to memorize until then.
1.7 Strings
A last program for this chapter: print the longest line of the input.
// Print the longest input line.
var longest = ""
while let line = readLine() {
if line.count > longest.count {
longest = line
}
}
if !longest.isEmpty {
print("\(longest.count) characters: \(longest)")
}
$ ./longest < in.txt
14 characters: this is a test
K&R’s version of this program takes three functions and a #define MAXLINE 1000, and truncates any line longer than that. This one has no maximum, because
a Swift String grows to fit what you put in it and frees itself when the last
name referring to it goes away. You never write the allocation and you never
write the free.
longest = line looks like it copies the text and does not. Swift strings and
arrays share their storage until somebody writes to one of them, at which point
the writer gets its own copy. You get the behavior of copying, which is simple
to reason about, and you pay for it only when a copy is actually needed. Chapter
7 is where that machinery gets explained; until then you can read every
assignment as a copy and never be wrong about what the program does.
line.count counts characters, and Chapter 6 will point out that this walks the
whole string rather than reading a stored length. On the lines of a text file
nobody notices.
Exercise 1-12. Print all lines longer than 80 characters.
Exercise 1-13. Print each input line with trailing blanks and tabs removed, and skip lines that are empty afterwards.
Exercise 1-14. Print a histogram of the lengths of words in the input, one
line per length, using * for the bars.
1.8 What You Have Seen
Enough Swift to write small programs: let and var and the preference for the
first, the numeric types and the refusal to mix them silently, while and for
over ranges, switch with its insistence on covering every case, functions with
labeled arguments, and two things that will each get a chapter, in readLine
handing back a value that might not be there and in the promise that assigning a
string copies it.
What you have not seen is why any of it is arranged this way. The next chapters answer that, and Chapters 5 and 7 answer the largest part of it.