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

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.