Struct vs Class Swift: Complete Guide to Value Types and Reference Types
In Swift, structs are value types that employ copy-on-write semantics and stack allocation, while classes are reference types that utilize heap allocation, support inheritance, and are managed by Automatic Reference Counting (ARC).
When developing with the apple/swift open-source toolchain, choosing between struct and class determines your data's memory layout, thread safety, and lifecycle management. The Swift compiler distinguishes these categories at the abstract syntax tree (AST) level using StructDecl and ClassDecl nodes, enforcing distinct semantics from compilation through runtime execution.
Value Semantics vs. Reference Semantics
The fundamental distinction in struct vs class Swift development lies in their assignment behavior.
Structs implement value semantics. When you assign a struct instance to a new variable or pass it to a function, Swift creates a complete copy of the data. According to the implementation in stdlib/public/core/String.swift, standard library types like String and Array use copy-on-write optimizations to maintain efficiency while preserving value semantics.
Classes implement reference semantics. Assignment operations copy only the reference (pointer) to a heap-allocated object. Multiple variables can point to the same instance, and mutations are visible through all references. As shown in stdlib/private/StdlibUnicodeUnittest/Collation.swift, reference types are essential when object identity must be preserved across different scopes.
Memory Allocation and Performance Characteristics
Memory management represents a critical technical difference between these type categories.
Stack Allocation for Structs
Value types are typically allocated on the stack or inlined within containing structures. This eliminates heap allocation overhead and reference-counting operations. The Swift compiler generates efficient value-type instructions at the SIL (Swift Intermediate Language) level, as documented in SwiftCompilerSources/Sources/SIL/Value.swift.
Heap Allocation for Classes
Classes require heap allocation with pointer indirection. Each class instance carries reference-counting metadata managed by ARC. The compiler injects retain and release calls for class types, whereas structs never trigger these passes. In SwiftCompilerSources/Sources/SIL/Instruction.swift, you can observe reference-specific instructions like ref_element_addr and ref_cast that handle class field access and type casting.
Inheritance and Protocol Conformance
The object-oriented capabilities of classes contrast sharply with the protocol-oriented design of structs.
Class Inheritance
Classes support single inheritance, allowing subclasses to override methods and properties. You can mark classes as final to prevent subclassing, or use required initializers to enforce implementation in subclasses. This inheritance hierarchy is managed through the ClassDecl AST node defined in SwiftCompilerSources/Sources/AST/Declarations.swift at line 125.
Struct Protocol Composition
Structs cannot inherit from other structs or classes. Instead, they rely on protocol conformance and composition to achieve polymorphism. The StructDecl node (line 121 in Declarations.swift) enforces these restrictions during semantic analysis. This design encourages protocol-oriented programming, where structs adopt protocols to gain capabilities without the overhead of inheritance hierarchies.
Identity, Equality, and Mutability
Reference identity and mutation semantics differ significantly between the two type categories.
Identity Operators
Classes support the identity operator (===) to determine if two references point to the same heap instance. Structs lack intrinsic identity; you cannot use === with value types. Equality for structs is determined by comparing all stored properties.
Mutating Methods
Structs require the mutating keyword on methods that modify self, explicitly indicating that the method changes the value. This requirement stems from value semantics where the entire instance may be replaced. Classes do not use mutating; instance methods can modify properties through any reference unless the instance itself is declared with let.
Practical Code Examples
Value Semantics with Copy-on-Write
The following example demonstrates how structs create independent copies:
struct Point {
var x: Double
var y: Double
mutating func move(dx: Double, dy: Double) {
x += dx
y += dy
}
}
var a = Point(x: 0, y: 0)
var b = a // Creates a full copy of the value
b.move(dx: 5, dy: 5) // Mutates only b
print(a) // Point(x: 0.0, y: 0.0) - unchanged
print(b) // Point(x: 5.0, y: 5.0)
Reference Semantics and Identity
This example illustrates class behavior with shared references:
class Counter {
var value = 0
func increment() {
value += 1
}
}
let c1 = Counter()
let c2 = c1 // Both reference the same heap instance
c2.increment()
print(c1.value) // 1 - visible through both references
print(c1 === c2) // true - identity check confirms same object
Inheritance vs. Protocol Composition
Demonstrating the architectural differences:
// Class inheritance hierarchy
class Vehicle {
var speed = 0
}
class Car: Vehicle {
var wheels = 4
}
// Struct with protocol conformance
protocol Drivable {
var speed: Int { get set }
}
struct Bike: Drivable {
var speed = 0
}
Managing Reference Cycles
Using weak references to prevent retain cycles in class hierarchies:
class Owner {
var pet: Pet?
}
class Pet {
weak var owner: Owner? // weak prevents retain cycle
}
var person = Owner()
var dog = Pet()
person.pet = dog
dog.owner = person
// No retain cycle - either can be deallocated independently
Summary
- Value vs. Reference: Structs use value semantics (copy-on-write) while classes use reference semantics (shared heap instances).
- Memory Management: Structs avoid ARC overhead and typically use stack allocation; classes require heap allocation and ARC management.
- Inheritance: Classes support single inheritance and identity checking (
===); structs use protocol composition and lack inheritance. - Compiler Implementation: The Swift compiler distinguishes these via
StructDecl(line 121) andClassDecl(line 125) inSwiftCompilerSources/Sources/AST/Declarations.swift, generating different SIL instructions for value types versus reference types. - Mutability: Structs require the
mutatingkeyword for self-modification; classes modify properties through any reference.
Frequently Asked Questions
When should I use a struct versus a class in Swift?
Use structs for simple data containers, mathematical types, and models that represent values without identity, such as coordinates or configuration options. Use classes when you need reference identity, inheritance hierarchies, or shared mutable state across multiple parts of your application, such as view controllers or network managers.
Why do structs provide better thread safety than classes?
Structs provide natural thread safety through value semantics. When you pass a struct to another thread, Swift creates a copy of the data, ensuring that mutations in one thread cannot affect the instance in another thread. Classes share a single heap instance across threads, requiring explicit synchronization mechanisms like locks or actors to prevent data races.
What is the performance impact of using classes instead of structs?
Classes incur heap allocation overhead and ARC management costs that structs avoid. Each class instance requires memory allocation from the heap and reference counting operations when references are created or destroyed. Structs typically use stack allocation or inline storage, eliminating indirection and retain/release cycles. However, for large value types, copy-on-write optimizations in the standard library help mitigate copying costs.
Can structs inherit from other types in Swift?
No, structs cannot inherit from other structs or classes. Swift structs support protocol conformance and composition instead of inheritance. If you need inheritance behavior, you must use a class, which supports single inheritance from another class. This design encourages protocol-oriented programming and prevents the complexity of multiple inheritance hierarchies in value types.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →