Skip to content
Theory · Classes · lesson 3 of 10

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.

SWIFT
class Document {
    var title = "Plan"
}

let doc = Document()
doc.title = "Final"
print(doc.title) // Final

Let's see why this compiles:

  1. let doc = Document() creates an instance and puts a reference to it into the constant doc.
  2. doc.title = "Final" changes not the reference but a property of the instance. The reference in doc stays the same, so let forbids nothing here.
  3. print(doc.title) prints Final.

Tying the tag doc to another sheet, though, does not work. The line doc = Document() gives an error:

TEXT
error: cannot assign to value: 'doc' is a 'let' constant

If 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.

SWIFT
struct Form {
    var title = "Plan"
}

let form = Form()
form.title = "Final" // ✗
TEXT
error: cannot assign to property: 'form' is a 'let' constant

The 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:

SWIFT
let original = Document()
let draft = original
draft.title = "Draft"
print(original.title) // Draft

Let's go through it line by line:

  1. original and draft are two constants holding one and the same reference.
  2. draft.title = "Draft" changes the shared instance.
  3. original.title is now Draft too: let did 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, let forbids both replacing the value and changing its properties;
  • on a class, let only forbids pointing the reference at another instance, while the instance's var properties still change.

Common mistake

A struct was turned into a class, and a method kept the word mutating:

SWIFT
class Counter {
    var count = 0
    mutating func increment() { // ✗
        count += 1
    }
}
TEXT
error: 'mutating' is not valid on instance methods in classes

The 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

  • let on a class reference only forbids tying it to another instance: doc = Document() does not compile.
  • The var properties of a class instance change through any reference, even through a let constant.
  • let on 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 with let inside the class.