Structs & Classes
Understand value vs reference semantics, mutating methods, initializers, inheritance, deinitializers, and when to choose a struct over a class.
Introduction
Structs and classes are Swift's two main tools for grouping related data and behavior into a custom type. They look similar at first glance — both can have properties, methods, and initializers — but they differ in one fundamental way: how copies of them behave. Getting this distinction right is one of the most important skills in writing correct Swift.
This lesson covers defining both, the value-vs-reference semantics that separate them, mutating methods, inheritance (classes only), deinitializers, and practical guidance on which to reach for.
Defining Structs
A `struct` bundles related properties together. Swift automatically generates a memberwise initializer for structs, so you don't have to write one yourself just to set initial values.
struct Point { var x: Double var y: Double}
var p1 = Point(x: 0, y: 0)var p2 = p1p2.x = 10
print(p1.x)print(p2.x)Click Run to see what this code prints.
Assigning `p1` to `p2` created an entirely independent copy — changing `p2.x` had no effect on `p1.x` at all. This is value semantics, and it's the default behavior for every struct in Swift.
Defining Classes
A `class` looks almost identical to a struct at first glance, but classes do not get a free memberwise initializer, and — critically — instances are shared by reference rather than copied.
class Counter { var count = 0 func increment() { count += 1 }}
let c1 = Counter()let c2 = c1c2.increment()
print(c1.count)print(c2.count)Click Run to see what this code prints.
Value vs Reference Semantics
This is the single most important distinction between the two: assigning or passing a struct copies its entire value, so each copy is fully independent. Assigning or passing a class instance copies only the reference — both variables point at the exact same underlying object in memory, so a change through one is visible through the other, as the `Counter` example above demonstrated.
Mutating Methods
Because struct instances are values, a method on a struct cannot modify its own properties unless it's explicitly marked `mutating`. This makes accidental mutation impossible by default — the compiler simply won't allow it without the keyword.
struct Point2 { var x: Double var y: Double
mutating func moveBy(dx: Double, dy: Double) { x += dx y += dy }}
var origin = Point2(x: 0, y: 0)origin.moveBy(dx: 5, dy: 5)print(origin.x, origin.y)Click Run to see what this code prints.
Initializers
Structs get a free memberwise initializer automatically, generated from their stored properties. Classes never get one automatically — you must write an `init` yourself for any properties that don't have a default value.
class Animal { var name: String init(name: String) { self.name = name }}Inheritance
Only classes support inheritance in Swift — structs cannot inherit from another struct. A subclass can override a superclass's methods using the `override` keyword, which the compiler requires so overrides are always intentional and visible.
class Dog: Animal { override func speak() -> String { "\(name) says Woof!" }}
class Animal2 { var name: String init(name: String) { self.name = name } func speak() -> String { "\(name) makes a sound." }}
class Dog2: Animal2 { override func speak() -> String { "\(name) says Woof!" }}
let pet: Animal2 = Dog2(name: "Rex")print(pet.speak())Click Run to see what this code prints.
Deinitializers
Classes can define a `deinit` block, which Swift automatically calls right before an instance is deallocated by Automatic Reference Counting (ARC) — useful for cleanup like closing a file handle or invalidating a timer. Structs never have a `deinit` because, as value types, there's no shared instance lifetime to track — each copy simply exists on its own.
Identity Operators
Because class instances are references, Swift provides `===` and `!==` to check whether two variables point to the exact same instance in memory — this is different from `==`, which (when implemented via `Equatable`) compares whether two instances have equal values.
let counterA = Counter()let counterB = counterAlet counterC = Counter()
print(counterA === counterB) // true — same instanceprint(counterA === counterC) // false — different instancesChoosing Struct or Class
| Use a struct when... | Use a class when... |
|---|---|
| You want independent copies with no shared mutable state. | You need shared, mutable state across multiple owners. |
| The data models a simple value (a point, a color, a size). | You need identity — two instances that are "the same object." |
| You don't need inheritance. | You need to share behavior through class inheritance. |
| Most Swift standard library types (Array, String, Dictionary). | You need a deinitializer to clean up resources. |
Common Mistakes
- Assuming a struct assignment shares state like a class would — it always copies the full value.
- Forgetting the `mutating` keyword on a struct method that changes `self`, which is a compile-time error.
- Forgetting `override` when a subclass replaces a superclass method — Swift requires it explicitly.
- Defaulting to classes out of habit from other object-oriented languages, when a struct would be simpler and safer.
Best Practices
- Prefer structs by default, as Apple recommends and as most of the Swift standard library itself does.
- Reach for a class specifically when you need reference semantics, identity, or inheritance — not as a default habit.
- Mark every struct method that modifies its own properties with `mutating`.
- Use `===`/`!==` only for identity checks on classes; use `==` (via Equatable) to compare values.
Frequently Asked Questions
Yes, freely. A struct can hold a class instance as a property (that property is still a shared reference), and a class can hold struct properties (which are copied whenever the class instance itself is passed by reference, but not independently).
Value semantics make code easier to reason about — you never have to worry about a struct changing unexpectedly because some other part of the code holds a reference to it, which eliminates a whole category of shared-mutable-state bugs.
No, but they can conform to multiple protocols, which covers most of the code-reuse benefits people reach for inheritance for — covered in the Protocols & Extensions lesson.
Key Takeaways
- Structs are value types — assigning or passing one always creates an independent copy.
- Classes are reference types — assigning or passing one shares the same underlying instance.
- Struct methods that modify properties must be marked `mutating`.
- Only classes support inheritance, `override`, and `deinit`.
- Prefer structs by default; use classes when you specifically need identity, shared mutable state, or inheritance.
Summary
You now understand the crucial value-versus-reference distinction between structs and classes, and when to reach for each. Next, you'll learn how enums let you model a fixed set of related states with remarkable type safety.