Two ways to bundle data
Swift has two keywords for defining a type that groups data: struct and class. On the page they look almost the same:
struct PlayerStruct {
var score = 0
}
class PlayerClass {
var score = 0
}
Both give you a type with a mutable score that starts at 0, and both can be created with PlayerStruct() and PlayerClass(), because every property has a default value. The real difference shows up on assignment.
- A struct is a value type. Assigning it to a new name makes a full, independent copy.
- A class is a reference type. Assigning it copies only a reference, so both names point at one shared object.
Seeing the difference
This is the lesson's exercise. First the struct:
let structOriginal = PlayerStruct()
var structCopy = structOriginal
structCopy.score = 100
print("struct original = \(structOriginal.score)")
print("struct copy = \(structCopy.score)")
structCopy is its own value. Setting its score to 100 doesn't affect structOriginal, which still has 0. Then the class:
let classOriginal = PlayerClass()
let classCopy = classOriginal
classCopy.score = 100
print("class original = \(classOriginal.score)")
print("class copy = \(classCopy.score)")
There is only one PlayerClass object. Both constants refer to it, so the change made through classCopy is visible through classOriginal as well. The whole program prints:
struct original = 0
struct copy = 100
class original = 100
class copy = 100
Step by step
- Declare the struct
PlayerStructand give itvar score = 0. Swift infersIntfrom the initial value. - Declare the class
PlayerClasswith the same property. Only the keyword differs. - Create the struct original with
let structOriginal = PlayerStruct(). Aletis enough because it is never changed. - Copy it into a
var.var structCopy = structOriginalalready makes the independent copy. It needsvarbecause you change it next. - Change the copy, then print both with the labels
struct originalandstruct copy, using string interpolation:\(structOriginal.score). - Repeat with the class. Create
classOriginal, assign it toclassCopy, setclassCopy.score = 100, and print both with the labelsclass originalandclass copy. Both bindings can staylet.
The solution in Swift
struct PlayerStruct {
var score = 0
}
class PlayerClass {
var score = 0
}
let structOriginal = PlayerStruct()
var structCopy = structOriginal
structCopy.score = 100
print("struct original = \(structOriginal.score)")
print("struct copy = \(structCopy.score)")
let classOriginal = PlayerClass()
let classCopy = classOriginal
classCopy.score = 100
print("class original = \(classOriginal.score)")
print("class copy = \(classCopy.score)")
The let trap
A let constant holding a struct is fully frozen. If you write let copy = original and then copy.score = 100, the compiler refuses:
error: cannot assign to property: 'copy' is a 'let' constant
note: change 'let' to 'var' to make it mutable
The same applies to calling a mutating method on a struct stored in a let. The compiler reports that it cannot use a mutating member on an immutable value.
The opposite holds for classes. A let that holds a class instance only fixes the reference: it can never be pointed at another object. The object's own var properties can still change, which is why classCopy.score = 100 compiles with let.
Where this shows up
- Function parameters. A struct passed to a function is copied, so the function can't change the caller's value, and a
var q = pinside it changes only the local copy. A class instance passed to a function is shared, so the function can change the caller's object. - Identity. Two class references can be compared with
===to ask whether they point at the same object.classOriginal === classCopyistrue. Structs have no identity, only values. - Arrays are structs too.
var b = acopies an array, so changingb[0]leavesauntouched.
Choosing between them
Use a struct for plain data where each copy should live its own life, such as coordinates or settings. Use a class when several parts of the program must share and change one object, or when you need inheritance. Sharing is powerful but surprising: a change in one place silently shows up in every other place that holds the same reference.
Key takeaways
- Assigning a struct copies it; assigning a class copies only the reference.
- A copied struct must be declared with
varto be changed. letfreezes a struct completely, but only freezes the reference to a class.
