let and an object reference
Let's go back to the sheet and the tags on strings. If a name is declared with var, its tag can be untied and tied to another sheet. The tag of a let name is tied for good: it always leads to one and the same sheet. But that does not stop you from writing on the sheet itself.
class Document {
var title = "Plan"
}
let doc = Document()
doc.title = "Final"
print(doc.title) // FinalLet's see why this compiles:
let doc = Document()creates an instance and puts a reference to it into the constantdoc.doc.title = "Final"changes not the reference but a property of the instance. The reference indocstays the same, soletforbids nothing here.print(doc.title)printsFinal.
Tying the tag doc to another sheet, though, does not work. The line doc = Document() gives an error:
error: cannot assign to value: 'doc' is a 'let' constantIf doc were a var variable, this assignment would work, and doc would lead to the new instance.
let on a struct and on a class
A let constant forbids changing what lies in the constant itself. The difference between a struct and a class is what exactly lies there.
A struct
A struct constant holds the form itself, with all its fields. let seals it whole: you can neither replace the form nor correct a field on it.
struct Form {
var title = "Plan"
}
let form = Form()
form.title = "Final" // ✗error: cannot assign to property: 'form' is a 'let' constantThe program does not compile: changing a property of a struct means changing the value that lies in the constant form.
A class
A class constant holds only a reference — a tag. let fixes the tag, while the instance it leads to stays changeable. Even if all the names of an instance are constants, it can be changed through any of them:
let original = Document()
let draft = original
draft.title = "Draft"
print(original.title) // DraftLet's go through it line by line:
originalanddraftare two constants holding one and the same reference.draft.title = "Draft"changes the shared instance.original.titleis nowDrafttoo:letdid not protect the instance from an edit through another name.
The same happens with a function parameter. A parameter is a constant, but for a class it is a constant holding a reference. So the function can change the properties of the instance passed to it, and the calling code sees the edit.
Here the shared-document picture falls short. A link to a real online document can be view-only, but let makes no such link: the instance can still be changed through the constant. A field is protected another way: declare it with let in the class itself. Such a property gets its value in its declaration or in init, and after that it cannot be changed through any reference.
Interviewers often ask how let on a struct differs from let on a class reference. The answer:
- on a struct,
letforbids both replacing the value and changing its properties; - on a class,
letonly forbids pointing the reference at another instance, while the instance'svarproperties still change.
Common mistake
A struct was turned into a class, and a method kept the word mutating:
class Counter {
var count = 0
mutating func increment() { // ✗
count += 1
}
}error: 'mutating' is not valid on instance methods in classesThe program does not compile. In a struct, mutating lets a method change properties, because the method changes the value itself. A method of a class changes the instance, not the reference, so it does not need the word mutating, and the compiler forbids it.
Remove mutating. A class method that changes properties can be called even on a let constant: let clicks = Counter() followed by clicks.increment() compiles.
Key points
leton a class reference only forbids tying it to another instance:doc = Document()does not compile.- The
varproperties of a class instance change through any reference, even through aletconstant. leton a struct freezes the whole value: you can neither replace it nor change its properties.- Class methods change properties without
mutating, and a field that must not change is declared withletinside the class.