Appendix C: Reading Compiler Errors
Swift’s diagnostics are good and the notes attached to them are often the
literal fix, so the habit worth building is reading all of a message rather than
the first line. Every message quoted here came from compiling the file of that
name in examples/errors, and tools/check.sh confirms each still says what it
says.
The shape is always the same. A location, a severity, a message. Then the source
around it, with the relevant span marked. Then zero or more note lines, which
either explain where the other half of the problem is or tell you what to type.
Some messages end with a name in brackets, such as [#NonSendableExitingActor],
and a matching line at the bottom giving a URL. That is the diagnostic’s group,
and the page it links to is a written explanation of the whole class of mistake
rather than this one instance of it. The concurrency errors are where you will
want it most, and they are the ones that have it.
let and var
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 points at the declaration, not the assignment, which is the pattern to expect: Swift shows you both ends of a contradiction. The fix is the note, unless the real answer is that you meant to make a new value instead.
Types do not mix
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)
The note lists what would have worked, which tells you that the operator is not
missing, only that neither side will move on its own. Convert one:
fahr + Double(step).
wrong_type.swift:1:20: error: cannot convert value of type 'Int' to specified type 'String'
1 | let name: String = 5
| `- error: cannot convert value of type 'Int' to specified type 'String'
When this appears far from where you think the problem is, the annotation you
wrote is usually fighting an inference Swift made several lines earlier. Deleting
the annotation and printing type(of: x) is the fastest way to find out what it
decided.
Nothing is truthy
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
The suggested fix is rarely what you want. if !items.isEmpty is what the code
meant.
Optionals
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'
optional_math.swift:3:7: error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
3 | print(a + b)
| |- note: coalesce using '??' to provide a default when the optional value contains 'nil'
| `- note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
The notes offer ?, ?? and !, and they are listed in the order you should
prefer them. The option they do not offer is the one that is usually right:
if let or guard let, which handles the empty case with code rather than with
a default or a crash.
switch must be exhaustive
non_exhaustive.swift:6:5: error: switch must be exhaustive
5 | func turn(_ d: Direction) -> String {
6 | switch d {
| |- error: switch must be exhaustive
| `- note: add missing case: '.west'
The note names the missing case. Resist adding a default to silence it: the
default is what stops this message appearing the next time somebody adds a
case, and this message is the feature.
Structs and mutation
missing_mutating.swift:3:21: error: left side of mutating operator isn't mutable: 'self' is immutable
3 | func bump() { n += 1 }
| | `- error: left side of mutating operator isn't mutable: 'self' is immutable
| `- note: mark method 'mutating' to make 'self' mutable
mutating_let.swift:7:9: error: cannot use mutating member on immutable value: 'counter' is a 'let' constant
6 | let counter = Counter()
| `- note: change 'let' to 'var' to make it mutable
7 | counter.bump()
The second is the first one’s consequence at the call site. If the note’s advice
feels wrong, it may be: a type whose methods all want to be mutating is
sometimes a type that should be a class, and more often a type whose methods
should return a new value.
Strings are not arrays of bytes
string_int_index.swift:2:7: error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.
1 | let name = "Ada Lovelace"
2 | print(name[0])
The unavailable marker means the standard library deliberately declared this
so it could give you a message rather than a “no such member”. name.first,
name.prefix(n), and name[name.index(name.startIndex, offsetBy: n)] are the
three things people usually want.
Errors
missing_try.swift:7:9: error: call can throw but is not marked with 'try'
7 | let n = risky()
| |- note: did you mean to use 'try'?
| |- note: did you mean to handle error as optional value?
| `- note: did you mean to disable error propagation?
Three notes, three answers: try, try?, try!. The first is the default and
requires the enclosing function to throw or to have a do-catch.
Closures
escaping.swift:2:21: error: converting non-escaping parameter 'handler' to generic parameter 'Element' may allow it to escape
1 | func register(_ handler: () -> Void, into handlers: inout [() -> Void]) {
2 | handlers.append(handler)
| `- error: converting non-escaping parameter 'handler' to generic parameter 'Element' may allow it to escape
“May allow it to escape” means the closure is being stored somewhere that
outlives the call. Mark the parameter @escaping.
Concurrency
data_race.swift:10:13: error: non-Sendable type 'Counter' of let 'counter' cannot exit main actor-isolated context [#NonSendableExitingActor]
1 | final class Counter {
| `- note: class 'Counter' does not conform to the 'Sendable' protocol
9 | group.addTask {
10 | counter.value += 1
[#NonSendableExitingActor]: <https://docs.swift.org/compiler/documentation/diagnostics/getting-non-sendable-values-out-of-actors>
This is the message that will take the longest to stop resenting. It is saying that a mutable class is about to be touched from two places at once. The note tells you what the type lacks, the linked page explains the class of mistake, and Chapter 12 lists the four fixes in the order to try them.
When the message is useless
Occasionally you get something vague, most often from a long expression with several literals and a generic call in it. The compiler has failed to find a solution and cannot tell you which part is impossible.
The reliable move is to break the expression up. Assign the pieces to named constants with explicit types, one line each, and the error attaches itself to the line that is actually wrong. This costs thirty seconds and beats staring at it. Once it compiles, put it back together if you still want to.