The Swift standard library reveals one striking fact: not only primitive types such as Int, Double, and Bool, but also String, Array, Dictionary, and Set are all structs. Things that are naturally classes in other languages are value types in Swift. Java’s String is a class, and in Python everything is an object reference.
This is not accidental. Swift established “value types as the default” during its design, then made that direction official in WWDC 2015’s famous sessions “Protocol-Oriented Programming” and “Building Better Apps with Value Types.” This article summarizes why Swift defaults to values instead of references and how that choice shaped the language.
This is part 3 of the Swift philosophy series. The syntax differences between classes and structs are covered separately, so here we focus on why that distinction was designed.
The Chronic Problems of a Reference-First World
To understand value types first, we must first examine the problems of a world where reference types are the default.
The essence of reference types is sharing. Copying a variable still leaves one object, with both variables pointing to it. When intentional, sharing is a feature; when accidental, it becomes a breeding ground for bugs. The classic pattern looks like this.
// Assume a reference type (class)
let settings = defaultSettings
settings.fontSize = 20 // I didn’t intend to touch the default settings
// defaultSettings.fontSizeand 20this happened
You thought you had copied it, but you were sharing it. The nasty part is that the symptom and cause are far apart. The code that corrupted the value may be several files away, turning debugging into “trace every reference to this object.”
Objective-C developers knew this problem well and traditionally defended against it. They declared NSString properties with copy, separated mutable NSArray variants (NSMutableArray) from immutable ones, and routinely added defensive copy. These were patches that used developer discipline to compensate for problems caused by references being the default.
The Swift team saw it this way: if a problem requires defensive conventions every time, might the language default be wrong?
What Value Types Protect — Local Reasoning
The essential property of a value type is that copying it produces a genuinely separate value.
var a = [1, 2, 3]
var b = a
b.append(4)
// ais still [1, 2, 3]
This guarantees more than convenience: it enables local reasoning. When you pass an array to a function, a value type means you do not need to worry that the function might secretly modify your array. You can fully understand your variable’s state by reading only your code block. In a reference-type world, you had to consider who held an object and when they might change it across the entire program; value types reduce that scope to the function in front of you.
This connects directly to the Safe philosophy discussed in part 1. Just as optionals catch “forgetting nil” at compile time, value types eliminate the bug class of “unintended sharing” at the type level. A struct declared with let is also genuinely immutable. For a class instance, let means only that the reference cannot change; its contents still can.
This property has become even more valuable over time. In a multithreaded environment, a data race occurs when “multiple threads share the same memory,” but value types are not shared in the first place, removing the premise for a race. It is a natural consequence that Swift Concurrency identifies value types as representative Sendable types that can cross thread boundaries. A design decision from 2014 was reclaimed by the concurrency model of 2021.
“Isn’t Copying Expensive?” — The Copy-on-Write Answer
The first objection to value types first is always performance. If an array with 100,000 elements were copied in full every time it was passed to a function, wouldn’t that be impractical?
Swift’s answer is Copy-on-Write (CoW). Standard collections such as Array, Dictionary, Set, and String share their internal storage when assigned and perform a real copy only when one side is modified. Semantically, they are complete values whose mutations are invisible to each other, while costing about as much as references when only read.
var a = hugeArray // No copy; storage shared
let x = a[0] // Still no copy
a.append(1) // The first copy occurs at this moment
The important point is that CoW is a library implementation technique, not a language feature. It is built into standard collections, but is not automatically added to structs we create ourselves. If you need a custom value type containing large data, you must implement it yourself with isKnownUniquelyReferenced. This implementation detail is covered in a separate article about CoW.
There is also a performance benefit in the opposite direction. Small structs can live on the stack without heap allocation and have no reference counting, making them cheaper than classes. That is why types such as CGPoint are structs. So the intuition that “value types are slow” generally works in reverse in Swift.
How to Live Without Inheritance — Protocols and Composition
Value types first come with one trade-off: structs cannot inherit. Because partial polymorphism is difficult without references, how do we achieve code reuse and polymorphism?
Swift’s answer is protocol-oriented programming (POP). Declare shared interfaces as protocols, put shared implementations in protocol extensions, and compose capabilities by having types adopt multiple protocols. Inheritance is a vertical structure where everything comes from one parent; protocol adoption is horizontal, letting you select and attach the capabilities you need.
struct Player: Codable, Equatable, Comparable {
let name: String
let score: Int
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.score < rhs.score
}
}
This struct inherits from nothing, yet supports JSON conversion, equality comparison, and sorting. Codable and Equatable are even synthesized automatically by the compiler. The combination of value types and protocols replaces most practical uses of inheritance.
That is why value types first and protocol-oriented design come as a set. It was no coincidence that the two sessions were presented side by side at WWDC 2015. “Structs and protocols instead of class inheritance” is Swift’s default combination, and the entire standard library is built this way. POP itself is covered in detail in a separate article.
So When Should You Use Classes?
Value types first does not mean “never use classes.” The precise rule is “struct by default; class when there is evidence that reference semantics are needed.” There are roughly three such cases.
When identity matters. Things that are “distinct entities even when their values match,” such as database connections, views on screen, and file handles, naturally use references. Two connections with identical settings are not the same connection.
When sharing itself is the goal. Shared models that multiple screens must observe and managers that must have a single app-wide instance benefit from reference sharing as a feature.
When lifetime management is needed. Use a class when deinit must release resources or when interacting with Objective-C frameworks such as UIKit.
Apple’s official documentation recommends the same direction: use structs and enums by default, choosing classes when these conditions apply. In practice, SwiftUI-era app code is converging on views as structs, state data as structs, and a small number of reference models (@Observable classes). The philosophy that values are the default and references the exception reaches the UI framework level.
Summary
- The Swift standard library is almost entirely structs by design. The default choice is a value type.
- The goal is to eliminate at the type level the distant bugs caused by unintended sharing in reference-first languages.
- Value types preserve local reasoning, and this property was reclaimed by Sendable in Swift Concurrency.
- Copy-on-Write in standard collections and stack allocation for small structs answer the performance objection.
- Protocol-oriented programming fills the gap left by inheritance, and the two were designed as a set.
- Classes have been redefined as the tool to choose when identity, sharing, or lifetime management is required.
This concludes the Swift philosophy series on safety (part 1), the learning curve (part 2), and value types (part 3). Next, we cover Swift Evolution, the process by which these philosophies become part of the language. It is the journey from a syntax proposal receiving an SE-XXXX number to entering Swift.
Recommended Reading
- [Swift Philosophy #4] What is SE-0296? How Swift syntax is born: a complete Swift Evolution guide
- [Swift Intermediate #1] A complete guide to Swift ARC: choose weak vs unowned by lifetime relationships
- [Swift Intermediate #2] From Swift generics (Generics) basics to practical use: how
eliminates duplication and risk at once

![Cover image for [Swift Philosophy #3] Why Swift Uses Structs: Value Types First](/assets/images/posts/c6e5417c-1bad-4913-bdff-974f972e1a73/1.jpg)