A constant class can have its properties changed. A constant struct can't. This sounds inconsistent until you remember the signpost — and then it makes perfect sense.


There's a moment when you're learning Swift where something happens that makes absolutely no sense on the surface. You create a constant class instance — a let — and then you change one of its properties. And it works. Swift lets you do it without complaint.

Meanwhile, you try the same thing with a constant struct and Swift immediately shuts you down.

Same keyword. Different behavior. What's going on?

The answer, as with most things in the classes chapter, comes back to the signpost. 🍥


A Quick Reminder About Signposts

When you create a class instance and assign it to a variable, what you're actually holding is a signpost pointing to the data — not the data itself. The class instance lives somewhere in memory, and your variable just tells Swift where to find it.

This is the fundamental difference between classes and structs. Structs hold their data directly. Classes hold a reference to their data.

Keep that in mind, because it explains everything that follows.


The Constant That Isn't Constant

Here's the thing that trips people up:

class Ninja {
    var name: String
    var powerLevel: Int

    init(name: String, powerLevel: Int) {
        self.name = name
        self.powerLevel = powerLevel
    }
}

let naruto = Ninja(name: "Naruto", powerLevel: 9000)
naruto.powerLevel = 9001 // ✅ This works fine

Enter fullscreen mode Exit fullscreen mode

Wait. naruto is a let. How is this allowed?

Because let naruto doesn't mean "the Ninja named Naruto is frozen forever." It means "the signpost called naruto is locked in place — it will always point to this specific Ninja instance."

The signpost is constant. The data it points to is not.

Changing naruto.powerLevel doesn't move the signpost. It doesn't replace the Ninja with a different Ninja. It just walks to the end of the signpost and changes a value there. The signpost itself didn't go anywhere.


What let Actually Locks

So what would let naruto prevent?

This:

let naruto = Ninja(name: "Naruto", powerLevel: 9000)
naruto = Ninja(name: "Sasuke", powerLevel: 8500) // ❌ Not allowed

Enter fullscreen mode Exit fullscreen mode

That's what you can't do. You can't point naruto at a completely different Ninja. The signpost is locked — you can't turn it to face a new direction. But you can absolutely change things at the end of the signpost.


Why Structs Work Differently

Now here's why constant structs don't allow property changes:

struct AnimeCharacter {
    var name: String
    var powerLevel: Int
}

let goku = AnimeCharacter(name: "Goku", powerLevel: 9001)
goku.powerLevel = 9002 // ❌ This won't compile

Enter fullscreen mode Exit fullscreen mode

Structs don't use signposts. They hold their data directly. So let goku means "the value stored here is constant" — and that value includes everything inside it.

Think of it this way: a struct is like the number 5. When you write let x = 5, you're saying x is 5, permanently. You can't say "well, I'll keep x but just change one digit of the 5." The 5 is the whole thing. Changing any part of a struct means replacing the entire struct — and you can't replace a constant.

So with structs:

var goku = AnimeCharacter(name: "Goku", powerLevel: 9001)
goku.powerLevel = 9002 // ✅ Works — goku is a var, so the whole struct can be replaced

Enter fullscreen mode Exit fullscreen mode

With var goku, Swift can swap out the entire struct value for a new one with the updated property. With let goku, it can't, because that would mean destroying and recreating a constant.


The Four Combinations

This gives us four different situations, and they're all worth understanding:

1. Constant class, constant property

let naruto = Ninja(name: "Naruto", powerLevel: 9000)
// naruto.powerLevel = 9001 ❌ — can't change a let property
// naruto = Ninja(...) ❌ — can't reassign the signpost

Enter fullscreen mode Exit fullscreen mode

The signpost is locked, and the name tag is written in permanent ink. Nothing changes.

2. Constant class, variable property

let naruto = Ninja(name: "Naruto", powerLevel: 9000)
naruto.powerLevel = 9001 // ✅ — can change the var property
// naruto = Ninja(...) ❌ — still can't reassign the signpost

Enter fullscreen mode Exit fullscreen mode

The signpost is locked, but the data at the end of it can still be updated. This is the one that surprises people.

3. Variable class, constant property

var naruto = Ninja(name: "Naruto", powerLevel: 9000)
// naruto.powerLevel = 9001 ❌ — can't change a let property
naruto = Ninja(name: "Sasuke", powerLevel: 8500) // ✅ — can point at a new Ninja

Enter fullscreen mode Exit fullscreen mode

You can swing the signpost to point at a completely different ninja, but once you're there, their permanent ink properties can't be changed.

4. Variable class, variable property

var naruto = Ninja(name: "Naruto", powerLevel: 9000)
naruto.powerLevel = 9001 // ✅
naruto = Ninja(name: "Sasuke", powerLevel: 8500) // ✅

Enter fullscreen mode Exit fullscreen mode

Maximum flexibility. The signpost can move, and the data can change. This is the most permissive option.


One Nice Bonus: No mutating Needed

Remember from the structs chapter how methods that changed properties had to be marked mutating? Classes don't have that requirement.

The reason structs need mutating is so Swift can protect you from calling a property-changing method on a constant struct — it can check at compile time whether the method would be allowed. With classes, the check is simpler: just look at whether the property itself is var or let. Swift doesn't need the mutating label to know what's allowed, because the class instance's own constancy doesn't affect its properties the same way.


The One Thing To Hold Onto

A let on a class locks the signpost, not the data. You can still change variable properties on a constant class — you just can't point that constant at a different instance entirely.

A let on a struct locks everything, because the struct is the data. There's no signpost to separate the container from the contents.

Once that distinction is clear, the four combinations stop feeling arbitrary and start feeling like exactly the right behavior for each type. 🌸


This article was written by me; AI was used to improve grammar and readability.